diff --git a/.coveragerc b/.coveragerc index d5127a71c..613a23c23 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,12 +1,15 @@ # .coveragerc to control coverage.py [run] branch = True -source = datajunction -# omit = bad_file.py +source = dj +omit = + */dj/sql/parsing/backends/grammar/generated/* + */dj/sql/parsing/backends/antlr4.py + */dj/sql/parsing/ast.py [paths] source = - src/ + dj/ */site-packages/ [report] @@ -26,3 +29,5 @@ exclude_lines = # Don't complain if non-runnable code isn't run: if 0: if __name__ == .__main__.: + + if TYPE_CHECKING: diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..b2ecf68fd --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,15 @@ +### Summary + + + +### Test Plan + + + +- [ ] PR has an associated issue: # +- [ ] `make check` passes +- [ ] `make test` shows 100% unit test coverage + +### Deployment Plan + + diff --git a/.github/workflows/bump-version.yml b/.github/workflows/bump-version.yml new file mode 100644 index 000000000..a48fb2820 --- /dev/null +++ b/.github/workflows/bump-version.yml @@ -0,0 +1,83 @@ +name: Bump Version +on: + workflow_dispatch: + inputs: + library: + type: choice + description: Which library to bump + required: true + options: + - server + - client + - djrs + - djqs + bump: + type: choice + description: Hatch version bump rule + required: true + options: + - release + - major + - minor + - patch + - alpha + - beta + - rc + - post + - dev + +jobs: + publish: + env: + PDM_DEPS: 'urllib3<2' + strategy: + fail-fast: false + matrix: + python-version: ['3.10'] + runs-on: 'ubuntu-latest' + defaults: + run: + working-directory: ${{ github.event.inputs.library == 'server' && '.' || github.event.inputs.library == 'client' && './datajunction-clients/python' || github.event.inputs.library == 'djrs' && './djrs' || github.event.inputs.library == 'djqs' && './djqs' }} + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install Hatch + run: | + python -m pip install --upgrade pip + pip install hatch + + - uses: pdm-project/setup-pdm@v3 + name: Setup PDM + with: + python-version: ${{ matrix.python-version }} + architecture: x64 + prerelease: true + enable-pep582: true + + - name: Configure Git + run: | + git config user.name "GitHub Actions Bot" + git config user.email "<>" + + - name: Bump release version with hatch and commit + run: | + hatch version ${{ github.event.inputs.bump }} + export NEW_VERSION=v$(hatch version) + export LIBRARY=${{ github.event.inputs.library }} + git commit -am "Bumping $LIBRARY to version $NEW_VERSION" + git checkout -b releases/$LIBRARY-$NEW_VERSION + git push --set-upstream origin releases/$LIBRARY-$NEW_VERSION -f + env: + GITHUB_TOKEN: ${{ secrets.REPO_SCOPED_TOKEN }} + + - name: Open a PR with the version bump + run: | + export NEW_VERSION=v$(hatch version) + export LIBRARY=${{ github.event.inputs.library }} + gh pr create -B main -H "releases/$LIBRARY-$NEW_VERSION" --title "Bumping $LIBRARY to version $NEW_VERSION" --body "This is an automated PR triggered by the Bump Version action. Merging this PR will bump the DataJunction $LIBRARY to version $NEW_VERSION" + env: + GITHUB_TOKEN: ${{ secrets.REPO_SCOPED_TOKEN }} diff --git a/.github/workflows/client-integration-tests.yml b/.github/workflows/client-integration-tests.yml new file mode 100644 index 000000000..61ef6ec46 --- /dev/null +++ b/.github/workflows/client-integration-tests.yml @@ -0,0 +1,53 @@ +name: Client Integration Tests +on: + schedule: + - cron: '0 12 * * *' + workflow_dispatch: +jobs: + python-client-integration: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ['3.8', '3.9', '3.10'] + steps: + - uses: actions/checkout@v2 + - name: Build and launch DJ demo environment + run: DOTENV_FILE="/code/datajunction-server/.env.integration" docker-compose --profile demo up -d + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - uses: pdm-project/setup-pdm@v3 + name: Setup PDM + with: + python-version: ${{ matrix.python-version }} + architecture: x64 + prerelease: true + enable-pep582: true + - name: Install dependencies + run: | + pdm sync -d + cd ./datajunction-clients/python; pdm install -d -G pandas + - name: Python client integration tests + run: cd datajunction-clients/python && make test PYTEST_ARGS="--integration -k test_integration" + + javascript-client-integration: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [18.x] + steps: + - uses: actions/checkout@v3 + - name: Build and launch DJ demo environment + run: docker-compose --profile demo up -d + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + - name: Install Dev Dependencies + run: npm install --only=dev + working-directory: ./datajunction-clients/javascript + - name: Javascript client integration tests + run: npm test + working-directory: ./datajunction-clients/javascript \ No newline at end of file diff --git a/.github/workflows/generate-openapi-client.yml b/.github/workflows/generate-openapi-client.yml new file mode 100644 index 000000000..c687ae4c9 --- /dev/null +++ b/.github/workflows/generate-openapi-client.yml @@ -0,0 +1,65 @@ +name: Generate OpenAPI Client +on: + workflow_dispatch: +jobs: + generate-python-client: + env: + PDM_DEPS: 'urllib3<2' + runs-on: ubuntu-latest + name: Generate Python Client + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.10" + + - name: Install DJ + run: | + python -m pip install --upgrade pip + pip install . + + - name: Generate OpenAPI Spec + run: ./scripts/generate-openapi.py -o openapi.json + + - name: Configure Git + run: | + git config user.name "GitHub Actions Bot" + git config user.email "<>" + + - name: Commit OpenAPI Spec + run: | + git add openapi.json + git commit -m "Updating OpenAPI Spec" + + - name: Generate Python client + uses: openapi-generators/openapitools-generator-action@v1.4.0 + with: + generator: python + openapi-file: openapi.json + config-file: ./.github/files/python-client-gen.yml + command-args: --skip-validate-spec + + - name: Move client to right directory + run: | + mkdir -p ./openapi/python + cp -r python-client/* ./openapi/python/ + rm -rf python-client + + - name: Set short sha + id: sha + run: echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + + - name: Commit generated files + run: | + git add openapi/python/ + git commit -m "Update DJ Python client" + git checkout -b ci-pr/python-client-${{ steps.sha.outputs.short_sha }} + git push --set-upstream origin ci-pr/python-client-${{ steps.sha.outputs.short_sha }} + + - name: Open a PR + run: gh pr create -B main -H ci-pr/python-client-${{ steps.sha.outputs.short_sha }} --title 'Update Python Client - ${{ steps.sha.outputs.short_sha }}' --body '(This PR was generated by a GitHub action)' + env: + GITHUB_TOKEN: ${{ secrets.REPO_SCOPED_TOKEN }} diff --git a/.github/workflows/generate-openapi-spec.yml b/.github/workflows/generate-openapi-spec.yml new file mode 100644 index 000000000..f527544b1 --- /dev/null +++ b/.github/workflows/generate-openapi-spec.yml @@ -0,0 +1,61 @@ +name: Generate OpenAPI Spec +on: + workflow_dispatch: +jobs: + generate-openapi-spec: + env: + PDM_DEPS: 'urllib3<2' + defaults: + run: + working-directory: ./datajunction-server + runs-on: ubuntu-latest + name: OpenAPI Spec + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.10" + + - name: Set up Node + uses: actions/setup-node@v3 + with: + node-version: 19 + + - name: Install DJ + run: | + python -m pip install --upgrade pip + pip install . + + - name: Generate OpenAPI Spec + run: | + ./scripts/generate-openapi.py -o ../openapi.json + + - name: Generate Markdown Docs from Spec + run: | + npm install -g widdershins + widdershins ../openapi.json -o ../docs/content/0.1.0/docs/developers/the-datajunction-api-specification.md --code=true --omitBody=true --summary=true + + - name: Configure Git + run: | + git config user.name "GitHub Actions Bot" + git config user.email "<>" + + - name: Set short sha + id: sha + run: echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + + - name: Commit OpenAPI Spec + run: | + git add ../openapi.json + git add ../docs/content/0.1.0/docs/developers/the-datajunction-api-specification.md + git commit -m "Updating OpenAPI Spec" + git checkout -b ci-pr/python-client-${{ steps.sha.outputs.short_sha }} + git push --set-upstream origin ci-pr/python-client-${{ steps.sha.outputs.short_sha }} + + - name: Open a PR + run: gh pr create -B main -H ci-pr/python-client-${{ steps.sha.outputs.short_sha }} --title 'Update OpenAPI Spec - ${{ steps.sha.outputs.short_sha }}' --body '(This PR was generated by a GitHub action)' + env: + GITHUB_TOKEN: ${{ secrets.REPO_SCOPED_TOKEN }} diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package-daily.yml similarity index 65% rename from .github/workflows/python-package.yml rename to .github/workflows/python-package-daily.yml index fdcc0287f..3e291e632 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package-daily.yml @@ -1,22 +1,20 @@ # This workflow will install Python dependencies, run tests and lint with a variety of Python versions # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions -name: Python package +name: Run tests with latest dependencies on: - push: - branches: [ main ] - pull_request: - branches: [ main ] + schedule: + - cron: '0 6 * * *' jobs: - build: + daily: runs-on: ubuntu-latest strategy: fail-fast: false matrix: - python-version: [3.8, 3.9, '3.10'] + python-version: ['3.8', '3.9', '3.10', '3.11.0rc1'] steps: - uses: actions/checkout@v2 @@ -24,10 +22,11 @@ jobs: uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - - name: Install dependencies + - name: Install latest dependencies run: | python -m pip install --upgrade pip pip install -e '.[testing]' - name: Test with pytest run: | - pytest --cov=src/datajunction -vv tests/ --doctest-modules src/datajunction + pre-commit run --all-files + pytest --cov-fail-under=100 --cov=dj -vv tests/ --doctest-modules dj --without-integration --without-slow-integration diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..867e89cdc --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,74 @@ +name: Release +on: + push: + branches: [ main ] + paths: + - 'dj/__about__.py' + - 'datajunction-clients/python/datajunction/__about__.py' + +jobs: + publish: + strategy: + fail-fast: false + matrix: + python-version: ['3.10'] + runs-on: 'ubuntu-latest' + defaults: + run: + working-directory: . + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install Hatch + run: | + python -m pip install --upgrade pip + pip install hatch + + - name: Configure Git + run: | + git config user.name "GitHub Actions Bot" + git config user.email "<>" + + - uses: dorny/paths-filter@v2 + id: changes + with: + filters: | + server: + - 'dj/__about__.py' + client: + - 'datajunction-clients/python/datajunction/__about__.py' + djrs: + - 'djrs/djrs/__about__.py' + djqs: + - 'djqs/djqs/__about__.py' + + - name: Tag release + working-directory: ${{ steps.changes.outputs.server == 'true' && '.' || steps.changes.outputs.client == 'true' && './datajunction-clients/python' || steps.changes.outputs.djrs == 'true' && './djrs' || steps.changes.outputs.djqs == 'true' && './djqs' }} + run: | + export NEW_VERSION=v$(hatch version) + export LIBRARY=${{ steps.changes.outputs.server == 'true' && 'server' || steps.changes.outputs.client == 'true' && 'client' || steps.changes.outputs.djrs == 'true' && 'djrs' || steps.changes.outputs.djqs == 'true' && 'djqs' }} + git tag -a $NEW_VERSION-$LIBRARY -m $NEW_VERSION-$LIBRARY + git push origin $NEW_VERSION-$LIBRARY + env: + GITHUB_TOKEN: ${{ secrets.REPO_SCOPED_TOKEN }} + + - name: Publish to pypi + working-directory: ${{ steps.changes.outputs.server == 'true' && '.' || steps.changes.outputs.client == 'true' && './datajunction-clients/python' || steps.changes.outputs.djrs == 'true' && './djrs' || steps.changes.outputs.djqs == 'true' && './djqs' }} + env: + HATCH_INDEX_USER: __token__ + HATCH_INDEX_AUTH: ${{ secrets.PYPI_TOKEN }} + run: | + hatch build + hatch publish + + - name: Create Github release + working-directory: ${{ steps.changes.outputs.server == 'true' && '.' || steps.changes.outputs.client == 'true' && './datajunction-clients/python' || steps.changes.outputs.djrs == 'true' && './djrs' || steps.changes.outputs.djqs == 'true' && './djqs' }} + run: | + export LIBRARY=${{ steps.changes.outputs.server == 'true' && 'server' || steps.changes.outputs.client == 'true' && 'client' || steps.changes.outputs.djrs == 'true' && 'djrs' || steps.changes.outputs.djqs == 'true' && 'djqs' }} + gh release create $(hatch version)-$LIBRARY + env: + GITHUB_TOKEN: ${{ secrets.REPO_SCOPED_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..932d6eea1 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,127 @@ +name: Run tests for DJ + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +concurrency: + group: ${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + env: + PDM_DEPS: 'urllib3<2' + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + python-version: ['3.8', '3.9', '3.10'] + library: ['client', 'server', 'djqs', 'djrs'] + + defaults: + run: + working-directory: ./ + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - uses: pdm-project/setup-pdm@v3 + name: Setup PDM + with: + python-version: ${{ matrix.python-version }} + architecture: x64 + prerelease: true + enable-pep582: true + + - name: Install dependencies + run: | + pdm sync -d + export tests_dir=${{ matrix.library == 'server' && './datajunction-server' || matrix.library == 'client' && './datajunction-clients/python' || matrix.library == 'djqs' && './datajunction-query' || matrix.library == 'djrs' && './datajunction-reflection'}} + cd $tests_dir; pdm install -d -G pandas + + - uses: pre-commit/action@v3.0.0 + name: Force check of all pdm.lock files + with: + extra_args: pdm-lock-check --all-files + + - name: Python Linters + run: | + export tests_dir=${{ matrix.library == 'server' && './datajunction-server' || matrix.library == 'client' && './datajunction-clients/python' || matrix.library == 'djqs' && './datajunction-query' || matrix.library == 'djrs' && './datajunction-reflection'}} + cd $tests_dir; pdm run pre-commit run --all-files + + - name: Test DJ ${{ matrix.library }} with pytest + run: | + export module=${{ matrix.library == 'server' && 'datajunction_server' || matrix.library == 'client' && 'datajunction' || matrix.library == 'djqs' && 'djqs' || matrix.library == 'djrs' && 'datajunction_reflection'}} + export tests_dir=${{ matrix.library == 'server' && './datajunction-server' || matrix.library == 'client' && './datajunction-clients/python' || matrix.library == 'djqs' && './datajunction-query' || matrix.library == 'djrs' && './datajunction-reflection'}} + cd $tests_dir; pdm run pytest ${{ matrix.library == 'server' && '-n auto' || '' }} --cov-fail-under=100 --cov=$module -vv tests/ --doctest-modules $module --without-integration --without-slow-integration + + build-javascript: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [18.x] + steps: + - uses: actions/checkout@v3 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + - name: Install Dev Dependencies + run: npm install --only=dev + working-directory: ./datajunction-clients/javascript + - name: Build Javascript Client + run: npm run build + working-directory: ./datajunction-clients/javascript + - name: Lint Javascript Client + run: npm run lint + working-directory: ./datajunction-clients/javascript + + build-ui: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./datajunction-ui + strategy: + matrix: + node-version: [19.x] + steps: + - name: Checkout repository + uses: actions/checkout@v3 + - name: Set up Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + - name: Install Dependencies + run: yarn install + - name: Run Unit Tests + run: yarn test + - name: Build Project + run: yarn webpack-build + + build-java: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./datajunction-clients/java + steps: + - uses: actions/checkout@v3 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + - name: Official Gradle Wrapper Validation Action + uses: gradle/wrapper-validation-action@v1 + - name: Build with Gradle + uses: gradle/gradle-build-action@v2 + with: + arguments: build -x test + build-root-directory: ./datajunction-clients/java \ No newline at end of file diff --git a/.gitignore b/.gitignore index 22e91ac24..d327bf8b8 100644 --- a/.gitignore +++ b/.gitignore @@ -81,9 +81,6 @@ celerybeat-schedule # SageMath parsed files *.sage.py -# dotenv -.env - # virtualenv .venv venv/ @@ -103,5 +100,20 @@ ENV/ .mypy_cache/ *.sqlite -*.db +dj.db +djqs.db *.swp + +# VS Code +.vscode + +# Idea +.idea + +# MacOS +.DS_Store +.pdm-python +.pdm.toml + +# oauth credentials +client_secret.json diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..b4a7f0d05 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "docs/themes/doks"] + path = docs/themes/doks + url = https://github.com/h-enk/doks.git diff --git a/.isort.cfg b/.isort.cfg index c8a204d9c..8c960bf43 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -1,3 +1,3 @@ [settings] profile = black -known_first_party = datajunction +known_first_party = dj diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 539485f0e..7d390df58 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,11 +1,11 @@ -exclude: '^docs/conf.py' +files: ^datajunction-(server|query|reflection)/ +exclude: (^docs/|^openapi/|^datajunction-clients/python/|^datajunction-clients/javascript/|^datajunction-server/dj/sql/parsing/backends/grammar/generated|^README.md) repos: -- repo: git://github.com/pre-commit/pre-commit-hooks - rev: v3.4.0 +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.1.0 hooks: - id: trailing-whitespace - - id: check-added-large-files - id: check-ast exclude: ^templates/ - id: check-json @@ -15,6 +15,7 @@ repos: - id: debug-statements exclude: ^templates/ - id: end-of-file-fixer + exclude: openapi.json - id: requirements-txt-fixer exclude: ^templates/ - id: mixed-line-ending @@ -32,12 +33,12 @@ repos: # ] - repo: https://github.com/pycqa/isort - rev: 5.7.0 + rev: 5.12.0 hooks: - id: isort - repo: https://github.com/psf/black - rev: 20.8b1 + rev: 22.8.0 hooks: - id: black language_version: python3 @@ -50,7 +51,7 @@ repos: # - id: blacken-docs # additional_dependencies: [black] -- repo: https://gitlab.com/pycqa/flake8 +- repo: https://github.com/PyCQA/flake8 rev: 3.9.2 hooks: - id: flake8 @@ -59,7 +60,7 @@ repos: # additional_dependencies: [flake8-bugbear] - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v0.910' # Use the sha / tag you want to point at + rev: 'v0.931' # Use the sha / tag you want to point at hooks: - id: mypy exclude: ^templates/ @@ -68,10 +69,9 @@ repos: - types-freezegun - types-python-dateutil - types-pkg_resources - - types-PyYAML - types-tabulate - repo: https://github.com/asottile/add-trailing-comma - rev: v2.1.0 + rev: v2.2.1 hooks: - id: add-trailing-comma #- repo: https://github.com/asottile/reorder_python_imports @@ -80,7 +80,7 @@ repos: # - id: reorder-python-imports # args: [--application-directories=.:src] - repo: https://github.com/hadialqattan/pycln - rev: v1.0.3 # Possible releases: https://github.com/hadialqattan/pycln/tags + rev: v2.1.7 # Possible releases: https://github.com/hadialqattan/pycln/tags hooks: - id: pycln args: [--config=pyproject.toml] @@ -89,7 +89,44 @@ repos: hooks: - id: pylint name: pylint - entry: pylint + entry: pylint --disable=duplicate-code,use-implicit-booleaness-not-comparison language: system types: [python] exclude: ^templates/ +- repo: https://github.com/kynan/nbstripout + rev: 0.6.1 + hooks: + - id: nbstripout +- repo: https://github.com/tomcatling/black-nb + rev: "0.7" + hooks: + - id: black-nb + files: '\.ipynb$' +- repo: https://github.com/pdm-project/pdm + rev: 2.8.1 + hooks: + - id: pdm-lock-check + name: pdm-lock-check-root + entry: pdm lock --check --project . + files: ^pyproject.toml$ +- repo: https://github.com/pdm-project/pdm + rev: 2.8.1 + hooks: + - id: pdm-lock-check + name: pdm-lock-check-server + entry: pdm lock --check --project datajunction-server + files: ^datajunction-server/pyproject.toml$ +- repo: https://github.com/pdm-project/pdm + rev: 2.8.1 + hooks: + - id: pdm-lock-check + name: pdm-lock-check-query + entry: pdm lock --check --project datajunction-query + files: ^datajunction-query/pyproject.toml$ +- repo: https://github.com/pdm-project/pdm + rev: 2.8.1 + hooks: + - id: pdm-lock-check + name: pdm-lock-check-reflection + entry: pdm lock --check --project datajunction-reflection + files: ^datajunction-reflection/pyproject.toml$ diff --git a/.pylintrc b/.pylintrc index 928bc4e0a..5b6fccaa9 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,6 +1,6 @@ [MESSAGES CONTROL] -disable = - duplicate-code [MASTER] +# https://github.com/samuelcolvin/pydantic/issues/1961#issuecomment-759522422 +extension-pkg-whitelist=pydantic ignore=templates,docs diff --git a/AUTHORS.rst b/AUTHORS.rst index 147dafe54..dcde57edb 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -3,3 +3,9 @@ Contributors ============ * Beto Dealmeida +* Olek Gorajek +* Hamidreza Hashemi +* Ali Raza +* Sam Redai +* Nick Ouellet +* Yian Shang diff --git a/CHANGELOG.rst b/CHANGELOG.md similarity index 100% rename from CHANGELOG.rst rename to CHANGELOG.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..18c914718 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 57763db20..5d466f4a5 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -1,353 +1,439 @@ -.. todo:: THIS IS SUPPOSED TO BE AN EXAMPLE. MODIFY IT ACCORDING TO YOUR NEEDS! - - The document assumes you are using a source repository service that promotes a - contribution model similar to `GitHub's fork and pull request workflow`_. - While this is true for the majority of services (like GitHub, GitLab, - BitBucket), it might not be the case for private repositories (e.g., when - using Gerrit). - - Also notice that the code examples might refer to GitHub URLs or the text - might use GitHub specific terminology (e.g., *Pull Request* instead of *Merge - Request*). - - Please make sure to check the document having these assumptions in mind - and update things accordingly. - -.. todo:: Provide the correct links/replacements at the bottom of the document. - -.. todo:: You might want to have a look on `PyScaffold's contributor's guide`_, - - especially if your project is open source. The text should be very similar to - this template, but there are a few extra contents that you might decide to - also include, like mentioning labels of your issue tracker or automated - releases. - - ============ Contributing ============ -Welcome to ``datajunction`` contributor's guide. - -This document focuses on getting any potential contributor familiarized -with the development processes, but `other kinds of contributions`_ are also -appreciated. - -If you are new to using git_ or have never collaborated in a project previously, -please have a look at `contribution-guide.org`_. Other resources are also -listed in the excellent `guide created by FreeCodeCamp`_ [#contrib1]_. - -Please notice, all users and contributors are expected to be **open, -considerate, reasonable, and respectful**. When in doubt, `Python Software -Foundation's Code of Conduct`_ is a good reference in terms of behavior -guidelines. - - -Issue Reports +Pre-requisites +============== + +DataJunction (DJ) is currently supported in Python 3.8, 3.9, and 3.10. It's recommended to use ``pyenv`` to create a virtual environment called "dj": + +.. code-block:: bash + + $ pyenv virtualenv 3.8 dj # or 3.9/3.10 + +Then you can pick any of the components you want to develop on, say ``cd datajunction-server`` or ``cd datajunction-clients/python``, +install required dependencies with ``pdm install`` and call ``make test`` to run all the unit tests for that component. + +DJ relies heavily on these libraries: + +- `sqloxide `_, for SQL parsing. +- `SQLAlchemy `_ for running queries and fetching table metadata. +- `SQLModel `_ for defining models. +- `FastAPI `_ for APIs. + +The SQLModel documentation is a good start, since it covers the basics of SQLAlchemy and FastAPI as well. + +Running the examples +==================== + +The repository should be run in conjunction with [`djqs`](https://github.com/DataJunction/djqs), which will load a +`postgres-roads` database with example data. In order to get this up and running: +* ``docker compose up`` from DJ to get the DJ metrics service up (defaults to run on port 8000) +* ``docker compose up`` in DJQS to get the DJ query service up (defaults to run on port 8001) + +Once both are up, you can fire up `juypter notebook` to run the `Modeling the Roads Example Database.ipynb`, +which will create all of the relevant nodes and provide some examples of API interactions. + +You can check that everything is working by querying the list of available catalogs (install `jq `_ if you don't have it): + +.. code-block:: bash + + % curl http://localhost:8000/catalogs/ | jq + [ + { + "name": "default", + "engines": [ + { + "name": "postgres", + "version": "", + "uri": "postgresql://dj:dj@postgres-roads:5432/djdb" + } + ] + } + ] + +To see the list of available nodes: + +.. code-block:: bash + + $ curl http://localhost:8000/nodes/ | jq + [ + { + "node_revision_id": 1, + "node_id": 1, + "type": "source", + "name": "repair_orders", + "display_name": "Repair Orders", + "version": "v1.0", + "status": "valid", + "mode": "published", + "catalog": { + "id": 1, + "uuid": "c2363d4d-ce0c-4eb2-9b1e-28743970f859", + "created_at": "2023-03-17T15:45:15.012784+00:00", + "updated_at": "2023-03-17T15:45:15.012795+00:00", + "extra_params": {}, + "name": "default" + }, + "schema_": "roads", + "table": "repair_orders", + "description": "Repair orders", + "query": null, + "availability": null, + "columns": [ + { + "name": "repair_order_id", + "type": "INT", + "attributes": [] + }, + { + "name": "municipality_id", + "type": "STR", + "attributes": [] + }, + { + "name": "hard_hat_id", + "type": "INT", + "attributes": [] + }, + { + "name": "order_date", + "type": "TIMESTAMP", + "attributes": [] + }, + { + "name": "required_date", + "type": "TIMESTAMP", + "attributes": [] + }, + { + "name": "dispatched_date", + "type": "TIMESTAMP", + "attributes": [] + }, + { + "name": "dispatcher_id", + "type": "INT", + "attributes": [] + } + ], + "updated_at": "2023-03-17T15:45:18.456072+00:00", + "materialization_configs": [], + "created_at": "2023-03-17T15:45:18.448321+00:00", + "tags": [] + }, + ... + ] + +And metrics: + +.. code-block:: bash + + $ curl http://localhost:8000/metrics/ | jq + [ + { + "id": 21, + "name": "num_repair_orders", + "display_name": "Num Repair Orders", + "current_version": "v1.0", + "description": "Number of repair orders", + "created_at": "2023-03-17T15:45:27.589799+00:00", + "updated_at": "2023-03-17T15:45:27.590304+00:00", + "query": "SELECT count(repair_order_id) as num_repair_orders FROM repair_orders", + "dimensions": [ + "dispatcher.company_name", + "dispatcher.dispatcher_id", + "dispatcher.phone", + "hard_hat.address", + "hard_hat.birth_date", + "hard_hat.city", + "hard_hat.contractor_id", + "hard_hat.country", + "hard_hat.first_name", + "hard_hat.hard_hat_id", + "hard_hat.hire_date", + "hard_hat.last_name", + "hard_hat.manager", + "hard_hat.postal_code", + "hard_hat.state", + "hard_hat.title", + "municipality_dim.contact_name", + "municipality_dim.contact_title", + "municipality_dim.local_region", + "municipality_dim.municipality_id", + "municipality_dim.municipality_type_desc", + "municipality_dim.municipality_type_id", + "municipality_dim.phone", + "municipality_dim.state_id", + "repair_orders.dispatched_date", + "repair_orders.dispatcher_id", + "repair_orders.hard_hat_id", + "repair_orders.municipality_id", + "repair_orders.order_date", + "repair_orders.repair_order_id", + "repair_orders.required_date" + ] + }, + { + "id": 22, + "name": "avg_repair_price", + "display_name": "Avg Repair Price", + "current_version": "v1.0", + "description": "Average repair price", + "created_at": "2023-03-17T15:45:28.121435+00:00", + "updated_at": "2023-03-17T15:45:28.121836+00:00", + "query": "SELECT avg(price) as avg_repair_price FROM repair_order_details", + "dimensions": [ + "repair_order.dispatcher_id", + "repair_order.hard_hat_id", + "repair_order.municipality_id", + "repair_order.repair_order_id", + "repair_order_details.discount", + "repair_order_details.price", + "repair_order_details.quantity", + "repair_order_details.repair_order_id", + "repair_order_details.repair_type_id" + ] + }, + ... + ] + + +To get data for a given metric: + +.. code-block:: bash + + $ curl http://localhost:8000/data/avg_repair_price/ | jq + +You can also pass query parameters to group by a dimension or filter: + +.. code-block:: bash + + $ curl "http://localhost:8000/data/avg_time_to_dispatch/?dimensions=dispatcher.company_name" | jq + $ curl "http://localhost:8000/data/avg_time_to_dispatch/?filters=hard_hat.state='AZ'" | jq + +Similarly, you can request the SQL for a given metric with given constraints: + +.. code-block:: bash + + $ curl "http://localhost:8000/sql/avg_time_to_dispatch/?dimensions=dispatcher.company_name" | jq + { + "sql": "SELECT avg(repair_orders.dispatched_date - repair_orders.order_date) AS avg_time_to_dispatch,\n\tdispatcher.company_name \n FROM \"roads\".\"repair_orders\" AS repair_orders\nLEFT JOIN (SELECT dispatchers.company_name,\n\tdispatchers.dispatcher_id,\n\tdispatchers.phone \n FROM \"roads\".\"dispatchers\" AS dispatchers\n \n) AS dispatcher\n ON repair_orders.dispatcher_id = dispatcher.dispatcher_id \n GROUP BY dispatcher.company_name" + } + +You can also run SQL queries against the metrics in DJ, using the special database with ID 0 and referencing a table called ``metrics``: + +.. code-block:: sql + + SELECT "basic.num_comments" + FROM metrics + WHERE "basic.source.comments.user_id" < 4 + GROUP BY "basic.source.comments.user_id" + + +API docs +======== + +Once you have Docker running you can see the API docs at http://localhost:8000/docs. + +Creating a PR ============= -If you experience bugs or general issues with ``datajunction``, please have a look -on the `issue tracker`_. If you don't see anything useful there, please feel -free to fire an issue report. - -.. tip:: - Please don't forget to include the closed issues in your search. - Sometimes a solution was already reported, and the problem is considered - **solved**. - -New issue reports should include information about your programming environment -(e.g., operating system, Python version) and steps to reproduce the problem. -Please try also to simplify the reproduction steps to a very minimal example -that still illustrates the problem you are facing. By removing other factors, -you help us to identify the root cause of the issue. - - -Documentation Improvements -========================== - -You can help improve ``datajunction`` docs by making them more readable and coherent, or -by adding missing information and correcting mistakes. - -``datajunction`` documentation uses Sphinx_ as its main documentation compiler. -This means that the docs are kept in the same repository as the project code, and -that any documentation update is done in the same way was a code contribution. - -.. todo:: Don't forget to mention which markup language you are using. - - e.g., reStructuredText_ or CommonMark_ with MyST_ extensions. - -.. todo:: If your project is hosted on GitHub, you can also mention the following tip: - - .. tip:: - Please notice that the `GitHub web interface`_ provides a quick way of - propose changes in ``datajunction``'s files. While this mechanism can - be tricky for normal code contributions, it works perfectly fine for - contributing to the docs, and can be quite handy. - - If you are interested in trying this method out, please navigate to - the ``docs`` folder in the source repository_, find which file you - would like to propose changes and click in the little pencil icon at the - top, to open `GitHub's code editor`_. Once you finish editing the file, - please write a message in the form at the bottom of the page describing - which changes have you made and what are the motivations behind them and - submit your proposal. - -When working on documentation changes in your local machine, you can -compile them using |tox|_:: - - tox -e docs +When creating a PR, make sure to run ``make test`` to check for test coverage. You can also run ``make check`` to run the pre-commit hooks. -and use Python's built-in web server for a preview in your web browser -(``http://localhost:8000``):: +A few `fixtures `_ are `available `_ to help writing unit tests. - python3 -m http.server --directory 'docs/_build/html' +Adding new dependencies +======================= +When a PR introduces a new dependency, add them to ``setup.cfg`` under ``install_requires``. If the dependency version is less than ``1.0`` and you expect it to change often it's better to pin the dependency, eg: -Code Contributions -================== +.. code-block:: config -.. todo:: Please include a reference or explanation about the internals of the project. + some-package==0.0.1 - An architecture description, design principles or at least a summary of the - main concepts will make it easy for potential contributors to get started - quickly. +Otherwise specify the package with a lower bound only: -Submit an issue ---------------- +.. code-block:: config -Before you work on any non-trivial code contribution it's best to first create -a report in the `issue tracker`_ to start a discussion on the subject. -This often provides additional considerations and avoids unnecessary work. + some-package>=1.2.3 -Create an environment ---------------------- +Don't use upper bounds in the dependencies. We have nightly unit tests that test if newer versions of dependencies will break. -Before you start coding, we recommend creating an isolated `virtual -environment`_ to avoid any problems with your installed Python packages. -This can easily be done via either |virtualenv|_:: +Database migrations +=================== - virtualenv - source /bin/activate +We use `Alembic `_ to manage schema migrations. If a PR introduces new models or changes existing ones a migration must be created. -or Miniconda_:: +1. Run the Docker container with ``docker compose up``. +2. Enter the ``dj`` container with ``docker exec -it dj bash``. +3. Run ``alembic revision --autogenerate -m "Description of the migration"``. This will create a file in the repository, under ``alembic/versions/``. Verify the file, checking that the upgrade and the downgrade functions make sense. +4. Still inside the container, run ``alembic upgrade head``. This will update the database schema to match the models. +5. Now run ``alembic downgrade $SHA``, where ``$SHA`` is the previous migration. You can see the hash with ``alembic history``. +6. Once you've confirmed that both the upgrade and downgrade work, upgrade again and commit the file. - conda create -n pyscaffold python=3 six virtualenv pytest pytest-cov - conda activate pyscaffold +If the migrations include ``alter_column`` or ``drop_column`` make sure to wrap them in a ``batch_alter_table`` context manager so that they work correctly with SQLite. You can see `an example here `_. -Clone the repository --------------------- +Development tips +=================== -#. Create an user account on |the repository service| if you do not already have one. -#. Fork the project repository_: click on the *Fork* button near the top of the - page. This creates a copy of the code under your account on |the repository service|. -#. Clone this copy to your local disk:: +Using ``PYTEST_ARGS`` with ``make test`` +---------------------------------------- - git clone git@github.com:YourLogin/datajunction.git - cd datajunction +If you'd like to pass additional arguments to pytest when running `make test`, you can define them as ``PYTEST_ARGS``. For example, you can include +`--fixtures` to see a list of all fixtures. -#. You should run:: +.. code-block:: sh - pip install -U pip setuptools -e . + make test PYTEST_ARGS="--fixtures" - to be able run ``putup --help``. +Running a Subset of Tests +------------------------- - .. todo:: if you are not using pre-commit, please remove the following item: +When working on tests, it's common to want to run a specific test by name. This can be done by passing ``-k`` as an additional pytest argument along +with a string expression. Pytest will only run tests which contain names that match the given string expression. -#. Install |pre-commit|_:: +.. code-block:: sh - pip install pre-commit - pre-commit install + make test PYTEST_ARGS="-k test_main_compile" - ``datajunction`` comes with a lot of hooks configured to automatically help the - developer to check the code being written. +Running TPC-DS Parsing Tests +------------------------- -Implement your changes ----------------------- +A TPC-DS test suite is included but skipped by default. As we incrementally build support for various SQL syntax into the DJ +SQL AST, it's helpful to run these tests using the `--tpcds` flag. -#. Create a branch to hold your changes:: +.. code-block:: sh - git checkout -b my-feature + make test PYTEST_ARGS="--tpcds" - and start making changes. Never work on the master branch! +You can run only the TPC-DS tests without the other tests using a `-k` filter. -#. Start your work on this branch. Don't forget to add docstrings_ to new - functions, modules and classes, especially if they are part of public APIs. +.. code-block:: sh -#. Add yourself to the list of contributors in ``AUTHORS.rst``. + make test PYTEST_ARGS="--tpcds -k tpcds" -#. When you’re done editing, do:: +Another useful option is matching on the full test identifier to run the test for a single specific query file from the +parametrize list. This is useful when paired with `--pdb` to drop into the debugger. - git add - git commit +.. code-block:: sh - to record your changes in git_. + make test PYTEST_ARGS="--tpcds --pdb -k test_parsing_ansi_tpcds_queries[./ansi/query1.sql]" - .. todo:: if you are not using pre-commit, please remove the following item: +If you prefer to use tox, these flags all work the same way. - Please make sure to see the validation messages from |pre-commit|_ and fix - any eventual issues. - This should automatically use flake8_/black_ to check/fix the code style - in a way that is compatible with the project. +.. code-block:: sh - .. important:: Don't forget to add unit tests and documentation in case your - contribution adds an additional feature and is not just a bugfix. + tox tests/sql/parsing/queries/tpcds/test_tpcds.py::test_parsing_sparksql_tpcds_queries -- --tpcds - Moreover, writing a `descriptive commit message`_ is highly recommended. - In case of doubt, you can check the commit history with:: +Enabling ``pdb`` When Running Tests +----------------------------------- - git log --graph --decorate --pretty=oneline --abbrev-commit --all +If you'd like to drop into ``pdb`` when a test fails, or on a line where you've added ``pdb.set_trace()``, you can pass ``--pdb`` as a pytest argument. - to look for recurring communication patterns. +.. code-block:: sh -#. Please check that your changes don't break any unit tests with:: + make test PYTEST_ARGS="--pdb" - tox +Using ``pdb`` In Docker +----------------------- - (after having installed |tox|_ with ``pip install tox`` or ``pipx``). +The included docker compose files make it easy to get a development environment up and running locally. When debugging or working on a new feature, +it's helpful to set breakpoints in the source code to drop into ``pdb`` at runtime. In order to do this while using the docker compose setup, there +are three steps. - You can also use |tox|_ to run several other pre-configured tasks in the - repository. Try ``tox -av`` to see a list of the available checks. +1. Set a trace in the source code on the line where you'd like to drop into ``pdb``. -Submit your contribution ------------------------- +.. code-block:: python -#. If everything works fine, push your local branch to |the repository service| with:: + import pdb; pdb.set_trace() - git push -u origin my-feature +2. In the docker compose file, enable ``stdin_open`` and ``tty`` on the service you'd like debug. -#. Go to the web page of your fork and click |contribute button| - to send your changes for review. +.. code-block:: YAML - .. todo:: if you are using GitHub, you can uncomment the following paragraph + services: + dj: + stdin_open: true + tty: true + ... - Find more detailed information `creating a PR`_. You might also want to open - the PR as a draft first and mark it as ready for review after the feedbacks - from the continuous integration (CI) system or any required fixes. +3. Once the docker environment is running, attach to the container. +.. code-block:: sh -Troubleshooting ---------------- + docker attach dj -The following tips can be used when facing problems to build or test the -package: +When the breakpoint is hit, the attached session will enter an interactive ``pdb`` session. -#. Make sure to fetch all the tags from the upstream repository_. - The command ``git describe --abbrev=0 --tags`` should return the version you - are expecting. If you are trying to run CI scripts in a fork repository, - make sure to push all the tags. - You can also try to remove all the egg files or the complete egg folder, i.e., - ``.eggs``, as well as the ``*.egg-info`` folders in the ``src`` folder or - potentially in the root of your project. +ANTLR +----- -#. Sometimes |tox|_ misses out when new dependencies are added, especially to - ``setup.cfg`` and ``docs/requirements.txt``. If you find any problems with - missing dependencies when running a command with |tox|_, try to recreate the - ``tox`` environment using the ``-r`` flag. For example, instead of:: +Generating the ANTLR Parser +--------------------------- - tox -e docs +Install the ANTLR generator tool. - Try running:: +.. code-block:: sh - tox -r -e docs + pip install antlr4-tools -#. Make sure to have a reliable |tox|_ installation that uses the correct - Python version (e.g., 3.7+). When in doubt you can run:: +While in the `dj/sql/parsing/backends/antlr4/grammar/` directory, generate the parser by running the following CLI command. - tox --version - # OR - which tox +.. code-block:: sh - If you have trouble and are seeing weird errors upon running |tox|_, you can - also try to create a dedicated `virtual environment`_ with a |tox|_ binary - freshly installed. For example:: + antlr4 -Dlanguage=Python3 -visitor SqlBaseLexer.g4 SqlBaseParser.g4 -o generated - virtualenv .venv - source .venv/bin/activate - .venv/bin/pip install tox - .venv/bin/tox -e all +A python 3 ANTLR parser will be generated in `dj/sql/parsing/backends/antlr4/grammar/generated/`. -#. `Pytest can drop you`_ in an interactive session in the case an error occurs. - In order to do that you need to pass a ``--pdb`` option (for example by - running ``tox -- -k --pdb``). - You can also setup breakpoints manually instead of using the ``--pdb`` option. +Creating a Diagram from the Grammar +----------------------------------- +Use https://bottlecaps.de/convert/ to go from ANTLR4 -> EBNF -Maintainer tasks -================ +Input the EBNF into https://bottlecaps.de/rr/ui -Releases --------- +Common Issues +=================== -.. todo:: This section assumes you are using PyPI to publicly release your package. +Docker missing dependencies +---------------------------------------- - If instead you are using a different/private package index, please update - the instructions accordingly. +If you are still new to docker development... you may run into this. If someone else modified / added new dependencies in some +of the DJ conatainers, you may notice an error like: -If you are part of the group of maintainers and have correct user permissions -on PyPI_, the following steps can be used to release a new version for -``datajunction``: +.. code-block:: sh + dj | ModuleNotFoundError: No module named 'sse_starlette' -#. Make sure all unit tests are successful. -#. Tag the current commit on the main branch with a release tag, e.g., ``v1.2.3``. -#. Push the new tag to the upstream repository_, e.g., ``git push upstream v1.2.3`` -#. Clean up the ``dist`` and ``build`` folders with ``tox -e clean`` - (or ``rm -rf dist build``) - to avoid confusion with old builds and Sphinx docs. -#. Run ``tox -e build`` and check that the files in ``dist`` have - the correct version (no ``.dirty`` or git_ hash) according to the git_ tag. - Also check the sizes of the distributions, if they are too big (e.g., > - 500KB), unwanted clutter may have been accidentally included. -#. Run ``tox -e publish -- --repository pypi`` and check that everything was - uploaded to PyPI_ correctly. +Remember it is not your local env that needs to be patched but one of the DJ docker conatainers. +Try running ``docker compose build``` and your ``docker compose up`` should work just fine. +Alembic migration error +---------------------------------------- -.. [#contrib1] Even though, these resources focus on open source projects and - communities, the general ideas behind collaborating with other developers - to collectively create software are general and can be applied to all sorts - of environments, including private companies and proprietary code bases. +If during development your alembic migrations get into a spot where the automatic upgrade (or downgrade) is stuck you may see something +similar to the following output in your ``db_migration`` agent log: +.. code-block:: sh -.. <-- strart --> -.. todo:: Please review and change the following definitions: + db_migration | sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) duplicate column name: categorical_partitions + db_migration | [SQL: ALTER TABLE availabilitystate ADD COLUMN categorical_partitions JSON] + db_migration | (Background on this error at: https://sqlalche.me/e/14/e3q8) + db_migration exited with code 1 -.. |the repository service| replace:: GitHub -.. |contribute button| replace:: "Create pull request" +The easiest way to fix it is to reset your database state using these commands (in another terminal session): -.. _repository: https://github.com//datajunction -.. _issue tracker: https://github.com//datajunction/issues -.. <-- end --> +.. code-block:: sh + $ docker exec -it dj bash + root@...:/code# -.. |virtualenv| replace:: ``virtualenv`` -.. |pre-commit| replace:: ``pre-commit`` -.. |tox| replace:: ``tox`` + root@...:/code# alembic downgrade base + ... + INFO [alembic.runtime.migration] Running downgrade e41c021c19a6 -> , Initial migration + root@...:/code# alembic upgrade head + ... -.. _black: https://pypi.org/project/black/ -.. _CommonMark: https://commonmark.org/ -.. _contribution-guide.org: http://www.contribution-guide.org/ -.. _creating a PR: https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request -.. _descriptive commit message: https://chris.beams.io/posts/git-commit -.. _docstrings: https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html -.. _first-contributions tutorial: https://github.com/firstcontributions/first-contributions -.. _flake8: https://flake8.pycqa.org/en/stable/ -.. _git: https://git-scm.com -.. _GitHub's fork and pull request workflow: https://guides.github.com/activities/forking/ -.. _guide created by FreeCodeCamp: https://github.com/FreeCodeCamp/how-to-contribute-to-open-source -.. _Miniconda: https://docs.conda.io/en/latest/miniconda.html -.. _MyST: https://myst-parser.readthedocs.io/en/latest/syntax/syntax.html -.. _other kinds of contributions: https://opensource.guide/how-to-contribute -.. _pre-commit: https://pre-commit.com/ -.. _PyPI: https://pypi.org/ -.. _PyScaffold's contributor's guide: https://pyscaffold.org/en/stable/contributing.html -.. _Pytest can drop you: https://docs.pytest.org/en/stable/usage.html#dropping-to-pdb-python-debugger-at-the-start-of-a-test -.. _Python Software Foundation's Code of Conduct: https://www.python.org/psf/conduct/ -.. _reStructuredText: https://www.sphinx-doc.org/en/master/usage/restructuredtext/ -.. _Sphinx: https://www.sphinx-doc.org/en/master/ -.. _tox: https://tox.readthedocs.io/en/stable/ -.. _virtual environment: https://realpython.com/python-virtual-environments-a-primer/ -.. _virtualenv: https://virtualenv.pypa.io/en/stable/ - -.. _GitHub web interface: https://docs.github.com/en/github/managing-files-in-a-repository/managing-files-on-github/editing-files-in-your-repository -.. _GitHub's code editor: https://docs.github.com/en/github/managing-files-in-a-repository/managing-files-on-github/editing-files-in-your-repository +After this, the `docker compose up` command should start the db_migration agent without problems. \ No newline at end of file diff --git a/Makefile b/Makefile deleted file mode 100644 index 8e5a6902a..000000000 --- a/Makefile +++ /dev/null @@ -1,24 +0,0 @@ -pyenv: .python-version - -.python-version: setup.cfg - if [ -z "`pyenv virtualenvs | grep datajunction`" ]; then\ - pyenv virtualenv datajunction;\ - fi - if [ ! -f .python-version ]; then\ - pyenv local datajunction;\ - fi - pip install -e '.[testing]' - touch .python-version - -test: pyenv - pytest --cov=src/datajunction -vv tests/ --doctest-modules src/datajunction - -clean: - pyenv virtualenv-delete datajunction - -spellcheck: - codespell -S "*.json" src/datajunction docs/*rst tests templates - -requirements.txt: .python-version - pip install --upgrade pip - pip-compile --no-annotate diff --git a/README.md b/README.md new file mode 100644 index 000000000..227950e2b --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +# DataJunction + +## Introduction + +DataJunction (DJ) is an open source **metrics platform** that allows users to define +metrics and the data models behind them using **SQL**, serving as a **semantic layer** +on top of a physical data warehouse. By leveraging this metadata, DJ can enable efficient +retrieval of metrics data across different dimensions and filters. + +[Documentation](http://datajunction.io) + +![DataJunction](docs/static/datajunction-illustration.png) + +## Getting Started + +To launch the DataJunction UI with a minimal DataJunction backend, start the default docker compose environment. + +```sh +docker compose up +``` + +If you'd like to launch the full suite of services, including open-source implementations of the DataJunction query service and +DataJunction reflection service specifications, use the `demo` profile. + +```sh +docker compose --profile demo up +``` + +DJUI: [http://localhost:3000/](http://localhost:3000/) +DJ Swagger Docs: [http://localhost:8000/docs](http://localhost:8000/docs) +DJQS Swagger Docs: [http://localhost:8001/docs](http://localhost:8001/docs) +Jaeger UI: [http://localhost:16686/search](http://localhost:16686/search) +Jupyter Lab: [http://localhost:8181](http://localhost:8181) + +## How does this work? + +At its core, DJ stores metrics and their upstream abstractions as interconnected nodes. +These nodes can represent a variety of elements, such as tables in a data warehouse +(**source nodes**), SQL transformation logic (**transform nodes**), dimensions logic, +metrics logic, and even selections of metrics, dimensions, and filters (**cube nodes**). + +By parsing each node's SQL into an AST and through dimensional links between columns, +DJ can infer a graph of dependencies between nodes, which allows it to find the +appropriate join paths between nodes to generate queries for metrics. diff --git a/README.rst b/README.rst deleted file mode 100644 index 42c81eb59..000000000 --- a/README.rst +++ /dev/null @@ -1,67 +0,0 @@ -.. These are examples of badges you might want to add to your README: - please update the URLs accordingly - - .. image:: https://api.cirrus-ci.com/github//datajunction.svg?branch=main - :alt: Built Status - :target: https://cirrus-ci.com/github//datajunction - .. image:: https://readthedocs.org/projects/datajunction/badge/?version=latest - :alt: ReadTheDocs - :target: https://datajunction.readthedocs.io/en/stable/ - .. image:: https://img.shields.io/coveralls/github//datajunction/main.svg - :alt: Coveralls - :target: https://coveralls.io/r//datajunction - .. image:: https://img.shields.io/pypi/v/datajunction.svg - :alt: PyPI-Server - :target: https://pypi.org/project/datajunction/ - .. image:: https://img.shields.io/conda/vn/conda-forge/datajunction.svg - :alt: Conda-Forge - :target: https://anaconda.org/conda-forge/datajunction - .. image:: https://pepy.tech/badge/datajunction/month - :alt: Monthly Downloads - :target: https://pepy.tech/project/datajunction - .. image:: https://img.shields.io/twitter/url/http/shields.io.svg?style=social&label=Twitter - :alt: Twitter - :target: https://twitter.com/datajunction - -.. image:: https://img.shields.io/badge/-PyScaffold-005CA0?logo=pyscaffold - :alt: Project generated with PyScaffold - :target: https://pyscaffold.org/ - -| - -============ -datajunction -============ - - - Add a short description here! - - -A longer description of your project goes here... - - -.. _pyscaffold-notes: - -Making Changes & Contributing -============================= - -This project uses `pre-commit`_, please make sure to install it before making any -changes:: - - pip install pre-commit - cd datajunction - pre-commit install - -It is a good idea to update the hooks to the latest version:: - - pre-commit autoupdate - -Don't forget to tell your contributors to also install and use pre-commit. - -.. _pre-commit: https://pre-commit.com/ - -Note -==== - -This project has been set up using PyScaffold 4.1. For details and usage -information on PyScaffold see https://pyscaffold.org/. diff --git a/TODO.mkd b/TODO.mkd deleted file mode 100644 index 3d03b90e9..000000000 --- a/TODO.mkd +++ /dev/null @@ -1,49 +0,0 @@ -- [ ] Single model for all nodes -- [ ] All nodes in the same directory -- [/] Compile repo, computing the schema of source nodes -- [ ] Auto-map dimensions from the DB schema -- [ ] Compute the schema of downstream nodes -- [ ] Translate metrics into SQL -- [ ] Compute metrics (ie, run query) -- [ ] Compute statistics on columns (histogram, correlation) -- [ ] Move data on JOINs based on column statistics -- [ ] Optimize data transfer (delta-of-delta for timeseries) -- [ ] Virtual dimensions (time, space, user-defined) -- [ ] JS dataframe with time-aware caching and additive-aware, to reuse queries -- [ ] 2 modes of join: Shillelagh and move data - -Models: - -- source -- transform -- dimension -- metric -- population - -Relationships: - -- source -> transform [-> transform ]-> metric -- source -> dimension -> population -- population based on metrics as well? - -DSL for querying: - -- `m=likes,comments&d=user.country&f=userid>10,time>2021-01-01T00:00:00+00:00` - -Examples: - -1. Dimension table in 2 storages (fast/slow), choose fast. -2. Dimension table in 2 storages (fast/slow) but only a few columns in fast; choose slow. -3. Translate query with cross-DB join, `FROM PROGRAM` in Postgres - -SQL input: - -``` -SELECT core.likes FROM metrics -``` - -Gets translated to: - -``` -SELECT COUNT(*) FROM content_actions -``` diff --git a/datajunction-clients/java/.gitignore b/datajunction-clients/java/.gitignore new file mode 100644 index 000000000..b63da4551 --- /dev/null +++ b/datajunction-clients/java/.gitignore @@ -0,0 +1,42 @@ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/datajunction-clients/java/build.gradle b/datajunction-clients/java/build.gradle new file mode 100644 index 000000000..3fe8f37b0 --- /dev/null +++ b/datajunction-clients/java/build.gradle @@ -0,0 +1,21 @@ +plugins { + id 'java' +} + +group = 'io.datajunction' +version = '1.0-SNAPSHOT' + +repositories { + mavenCentral() +} + +dependencies { + implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.15.2' + + testImplementation platform('org.junit:junit-bom:5.9.1') + testImplementation 'org.junit.jupiter:junit-jupiter' +} + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/datajunction-clients/java/gradle/wrapper/gradle-wrapper.jar b/datajunction-clients/java/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..249e5832f Binary files /dev/null and b/datajunction-clients/java/gradle/wrapper/gradle-wrapper.jar differ diff --git a/datajunction-clients/java/gradle/wrapper/gradle-wrapper.properties b/datajunction-clients/java/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..261c19f40 --- /dev/null +++ b/datajunction-clients/java/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Jul 24 21:03:58 EDT 2023 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/datajunction-clients/java/gradlew b/datajunction-clients/java/gradlew new file mode 100755 index 000000000..1b6c78733 --- /dev/null +++ b/datajunction-clients/java/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/datajunction-clients/java/gradlew.bat b/datajunction-clients/java/gradlew.bat new file mode 100644 index 000000000..ac1b06f93 --- /dev/null +++ b/datajunction-clients/java/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/datajunction-clients/java/settings.gradle b/datajunction-clients/java/settings.gradle new file mode 100644 index 000000000..7358c7e2d --- /dev/null +++ b/datajunction-clients/java/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'DataJunction' + diff --git a/datajunction-clients/java/src/main/java/io/datajunction/client/DJClient.java b/datajunction-clients/java/src/main/java/io/datajunction/client/DJClient.java new file mode 100644 index 000000000..0cbe17aa4 --- /dev/null +++ b/datajunction-clients/java/src/main/java/io/datajunction/client/DJClient.java @@ -0,0 +1,58 @@ +package io.datajunction.client; + +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.net.URI; +import java.io.IOException; + +public class DJClient { + + HttpClient client = HttpClient.newHttpClient(); + String baseURL; + String namespace; + String engineName; + String engineVersion; + + + public DJClient(HttpClient client,String baseURL, String namespace, String engineName,String engineVersion){ + this.client = client; + this.baseURL = baseURL; + this.namespace = namespace; + this.engineName = engineName; + this.engineVersion = engineVersion; + } + + public DJClient(String baseURL, String namespace, String engineName,String engineVersion){ + this.baseURL = baseURL; + this.namespace = namespace; + this.engineName = engineName; + this.engineVersion = engineVersion; + } + + public DJClient () { + this.baseURL = "http://localhost:8000"; + this.namespace = "default"; + this.engineName = null; + this.engineVersion = null; + } + + public HttpResponse getHelper(String endpoint) throws IOException, InterruptedException { + HttpRequest request = HttpRequest.newBuilder() + .GET() + .uri(URI.create(baseURL+endpoint)) + .setHeader("User-Agent", "Java 11 HttpClient Bot") // add request header + .build(); + + return client.send(request, HttpResponse.BodyHandlers.ofString()); + } + + public String listMetrics() throws IOException, InterruptedException { + HttpResponse response = getHelper("/metrics/"); + return response.body(); + } + + + + +} diff --git a/datajunction-clients/java/src/test/java/io/datajunction/client/DJClientTest.java b/datajunction-clients/java/src/test/java/io/datajunction/client/DJClientTest.java new file mode 100644 index 000000000..a679c5ea6 --- /dev/null +++ b/datajunction-clients/java/src/test/java/io/datajunction/client/DJClientTest.java @@ -0,0 +1,23 @@ +package io.datajunction.client; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.*; + +public class DJClientTest { + static DJClient client; + + @BeforeAll + public static void init() { + client = new DJClient(); + } + + @Test + public void testListMetrics() throws IOException, InterruptedException { + String metrics = client.listMetrics(); + assertEquals(metrics, "[\"default.num_repair_orders\",\"default.avg_repair_price\",\"default.total_repair_cost\",\"default.avg_length_of_employment\",\"default.total_repair_order_discounts\",\"default.avg_repair_order_discounts\",\"default.avg_time_to_dispatch\"]"); + } +} diff --git a/datajunction-clients/javascript/.babelrc b/datajunction-clients/javascript/.babelrc new file mode 100644 index 000000000..dc2fcbb01 --- /dev/null +++ b/datajunction-clients/javascript/.babelrc @@ -0,0 +1,8 @@ +{ + "presets": [ + "@babel/preset-env" + ], + "test": [ + "jest" + ] +} diff --git a/datajunction-clients/javascript/.eslintignore b/datajunction-clients/javascript/.eslintignore new file mode 100644 index 000000000..3e39984c5 --- /dev/null +++ b/datajunction-clients/javascript/.eslintignore @@ -0,0 +1 @@ +./dist/* \ No newline at end of file diff --git a/datajunction-clients/javascript/.eslintrc.js b/datajunction-clients/javascript/.eslintrc.js new file mode 100644 index 000000000..fb1c500a3 --- /dev/null +++ b/datajunction-clients/javascript/.eslintrc.js @@ -0,0 +1,13 @@ +module.exports = { + env: { + browser: true, + es2021: true, + }, + extends: 'standard', + overrides: [], + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + }, + rules: {}, +} diff --git a/datajunction-clients/javascript/.gitignore b/datajunction-clients/javascript/.gitignore new file mode 100644 index 000000000..6a7d6d8ef --- /dev/null +++ b/datajunction-clients/javascript/.gitignore @@ -0,0 +1,130 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* \ No newline at end of file diff --git a/src/datajunction/cli/__init__.py b/datajunction-clients/javascript/.npmignore similarity index 100% rename from src/datajunction/cli/__init__.py rename to datajunction-clients/javascript/.npmignore diff --git a/datajunction-clients/javascript/.prettierignore b/datajunction-clients/javascript/.prettierignore new file mode 100644 index 000000000..0f7a61c11 --- /dev/null +++ b/datajunction-clients/javascript/.prettierignore @@ -0,0 +1 @@ +./dist \ No newline at end of file diff --git a/datajunction-clients/javascript/.prettierrc b/datajunction-clients/javascript/.prettierrc new file mode 100644 index 000000000..e74ed9ff3 --- /dev/null +++ b/datajunction-clients/javascript/.prettierrc @@ -0,0 +1,6 @@ +{ + "trailingComma": "es5", + "tabWidth": 4, + "semi": false, + "singleQuote": true +} diff --git a/datajunction-clients/javascript/babel.config.js b/datajunction-clients/javascript/babel.config.js new file mode 100644 index 000000000..11687e217 --- /dev/null +++ b/datajunction-clients/javascript/babel.config.js @@ -0,0 +1 @@ +module.exports = {presets: ['@babel/preset-env']} \ No newline at end of file diff --git a/datajunction-clients/javascript/index.js b/datajunction-clients/javascript/index.js new file mode 100644 index 000000000..2d2f3c0bc --- /dev/null +++ b/datajunction-clients/javascript/index.js @@ -0,0 +1 @@ +module.exports = require('./dist') diff --git a/datajunction-clients/javascript/package-lock.json b/datajunction-clients/javascript/package-lock.json new file mode 100644 index 000000000..032126c58 --- /dev/null +++ b/datajunction-clients/javascript/package-lock.json @@ -0,0 +1,18296 @@ +{ + "name": "datajunction", + "version": "0.0.1-rc.1", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "datajunction", + "version": "0.0.1-rc.1", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.22.5", + "docker-names": "^1.2.1" + }, + "devDependencies": { + "@babel/cli": "^7.0.0", + "@babel/core": "^7.0.0", + "@babel/plugin-proposal-object-rest-spread": "^7.0.0", + "@babel/preset-env": "^7.0.0", + "babel-core": "^7.0.0-bridge.0", + "babel-jest": "^23.4.2", + "eslint": "^8.39.0", + "eslint-config-standard": "^17.0.0", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-n": "^15.7.0", + "eslint-plugin-promise": "^6.1.1", + "jest": "^29.5.0", + "prettier": "^2.8.8", + "webpack": "^5.81.0", + "webpack-cli": "^5.0.2" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/cli": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.22.5.tgz", + "integrity": "sha512-N5d7MjzwsQ2wppwjhrsicVDhJSqF9labEP/swYiHhio4Ca2XjEehpgPmerjnLQl7BPE59BLud0PTWGYwqFl/cQ==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.17", + "commander": "^4.0.1", + "convert-source-map": "^1.1.0", + "fs-readdir-recursive": "^1.1.0", + "glob": "^7.2.0", + "make-dir": "^2.1.0", + "slash": "^2.0.0" + }, + "bin": { + "babel": "bin/babel.js", + "babel-external-helpers": "bin/babel-external-helpers.js" + }, + "engines": { + "node": ">=6.9.0" + }, + "optionalDependencies": { + "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3", + "chokidar": "^3.4.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/cli/node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "optional": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@babel/cli/node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@babel/cli/node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "optional": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@babel/cli/node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "optional": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/@babel/cli/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@babel/cli/node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "optional": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@babel/cli/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/@babel/cli/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "optional": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@babel/cli/node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "optional": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@babel/cli/node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@babel/cli/node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "optional": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@babel/cli/node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/@babel/cli/node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/cli/node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@babel/cli/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "optional": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/@babel/cli/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/@babel/cli/node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/cli/node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "optional": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", + "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.5.tgz", + "integrity": "sha512-4Jc/YuIaYqKnDDz892kPIledykKg12Aw1PYX5i/TY28anJtacvM1Rrr8wbieB9GfEJwlzqT0hUEao0CxEebiDA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.5.tgz", + "integrity": "sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helpers": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/core/node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/core/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@babel/generator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.5.tgz", + "integrity": "sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.5.tgz", + "integrity": "sha512-m1EP3lVOPptR+2DwD125gziZNcmoNSHGmJROKoy87loWUQyJaVXDgpmruWqDARZSmtYQ+Dl25okU8+qhVzuykw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.5.tgz", + "integrity": "sha512-Ji+ywpHeuqxB8WDxraCiqR0xfhYjiDE/e6k7FuIaANnoOFxAHskHChz4vA1mJC9Lbm01s1PVAGhQY4FUKSkGZw==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.5", + "@babel/helper-validator-option": "^7.22.5", + "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.5.tgz", + "integrity": "sha512-xkb58MyOYIslxu3gKmVXmjTtUPvBU4odYzbiIQbWwLKIHCsx6UGZGX6F1IznMFVnDdirseUZopzN+ZRt8Xb33Q==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-member-expression-to-functions": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.5", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.5.tgz", + "integrity": "sha512-1VpEFOIbMRaXyDeUwUfmTIxExLwQ+zkW+Bh5zXpApA3oQedBx9v/updixWxnx/bZpKw7u8VxWjb/qWpIcmPq8A==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "regexpu-core": "^5.3.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "dev": true, + "dependencies": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.0.tgz", + "integrity": "sha512-RnanLx5ETe6aybRi1cO/edaRH+bNYWaryCEmjDDYyNr4wnSzyOp8T0dWipmqVHKEY3AbVKUom50AKSlj1zmKbg==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.17.7", + "@babel/helper-plugin-utils": "^7.16.7", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0-0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/helper-define-polyfill-provider/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", + "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", + "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz", + "integrity": "sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", + "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.5.tgz", + "integrity": "sha512-+hGKDt/Ze8GFExiVHno/2dvG5IdstpzCq0y4Qc9OJ25D4q3pKfiIP/4Vp3/JvhDkLKsDK2api3q3fpIgiIF5bw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.5.tgz", + "integrity": "sha512-cU0Sq1Rf4Z55fgz7haOakIyM7+x/uCFwXpLPaeRzfoUtAEAuUZjZvFPjL/rk5rW693dIgn2hng1W7xbT7lWT4g==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-wrap-function": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.5.tgz", + "integrity": "sha512-aLdNM5I3kdI/V9xGNyKSF3X/gTyMUBohTZ+/3QdQKAA9vxIiy12E+8E2HoOP1/DjeqU+g6as35QHJNMDDYpuCg==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-member-expression-to-functions": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", + "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz", + "integrity": "sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", + "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", + "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.5.tgz", + "integrity": "sha512-bYqLIBSEshYcYQyfks8ewYA8S30yaGSeRslcvKMvoUk6HHPySbxHq9YRi6ghhzEU+yhQv9bP/jXnygkStOcqZw==", + "dev": true, + "dependencies": { + "@babel/helper-function-name": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.5.tgz", + "integrity": "sha512-pSXRmfE1vzcUIDFQcSGA5Mr+GxBV9oiRKDuDxXvWQQBCh8HoIjs/2DlDB7H8smac1IVrB9/xdXj2N3Wol9Cr+Q==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", + "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz", + "integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.5.tgz", + "integrity": "sha512-NP1M5Rf+u2Gw9qfSO4ihjcTGW5zXTi36ITLd4/EoAcEhIZ0yjMqmftDNl3QC19CX7olhrjpyU454g/2W7X0jvQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.5.tgz", + "integrity": "sha512-31Bb65aZaUwqCbWMnZPduIZxCBngHFlzyN6Dq6KAJjtx+lx6ohKHubc61OomYi7XwVD4Ol0XCVz4h+pYFR048g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-proposal-object-rest-spread": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", + "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-unicode-property-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", + "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz", + "integrity": "sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz", + "integrity": "sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", + "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", + "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz", + "integrity": "sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.5.tgz", + "integrity": "sha512-gGOEvFzm3fWoyD5uZq7vVTD57pPJ3PczPUD/xCFGjzBpUosnklmXyKnGQbbbGs1NPNPskFex0j93yKbHt0cHyg==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.5", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz", + "integrity": "sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz", + "integrity": "sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.5.tgz", + "integrity": "sha512-EcACl1i5fSQ6bt+YGuU/XGCeZKStLmyVGytWkpyhCLeQVA0eu6Wtiw92V+I1T/hnezUv7j74dA/Ro69gWcU+hg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz", + "integrity": "sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.5.tgz", + "integrity": "sha512-SPToJ5eYZLxlnp1UzdARpOGeC2GbHvr9d/UV0EukuVx8atktg194oe+C5BqQ8jRTkgLRVOPYeXRSBg1IlMoVRA==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.5.tgz", + "integrity": "sha512-2edQhLfibpWpsVBx2n/GKOz6JdGQvLruZQfGr9l1qes2KQaWswjBzhQF7UDUZMNaMMQeYnQzxwOMPsbYF7wqPQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.5", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-classes/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz", + "integrity": "sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/template": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.5.tgz", + "integrity": "sha512-GfqcFuGW8vnEqTUBM7UtPd5A4q797LTvvwKxXTgRsFjoqaJiEg9deBG6kWeQYkVEL569NpnmpC0Pkr/8BLKGnQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz", + "integrity": "sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz", + "integrity": "sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.5.tgz", + "integrity": "sha512-0MC3ppTB1AMxd8fXjSrbPa7LT9hrImt+/fcj+Pg5YMD7UQyWp/02+JWpdnCymmsXwIx5Z+sYn1bwCn4ZJNvhqQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz", + "integrity": "sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g==", + "dev": true, + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.5.tgz", + "integrity": "sha512-X4hhm7FRnPgd4nDA4b/5V280xCx6oL7Oob5+9qVS5C13Zq4bh1qq7LU0GgRU6b5dBWBvhGaXYVB4AcN6+ol6vg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.5.tgz", + "integrity": "sha512-3kxQjX1dU9uudwSshyLeEipvrLjBCVthCgeTp6CzE/9JYrlAIaeekVxRpCWsDDfYTfRZRoCeZatCQvwo+wvK8A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz", + "integrity": "sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.5.tgz", + "integrity": "sha512-DuCRB7fu8MyTLbEQd1ew3R85nx/88yMoqo2uPSjevMj3yoN7CDM8jkgrY0wmVxfJZyJ/B9fE1iq7EQppWQmR5A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz", + "integrity": "sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.5.tgz", + "integrity": "sha512-MQQOUW1KL8X0cDWfbwYP+TbVbZm16QmQXJQ+vndPtH/BoO0lOKpVoEDMI7+PskYxH+IiE0tS8xZye0qr1lGzSA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz", + "integrity": "sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.22.5.tgz", + "integrity": "sha512-R+PTfLTcYEmb1+kK7FNkhQ1gP4KgjpSO6HfH9+f8/yfp2Nt3ggBjiVpRwmwTlfqZLafYKJACy36yDXlEmI9HjQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.5.tgz", + "integrity": "sha512-B4pzOXj+ONRmuaQTg05b3y/4DuFz3WcCNAXPLb2Q0GT0TrGKGxNKV4jwsXts+StaM0LQczZbOpj8o1DLPDJIiA==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.5.tgz", + "integrity": "sha512-emtEpoaTMsOs6Tzz+nbmcePl6AKVtS1yC4YNAeMun9U8YCsgadPNxnOPQ8GhHFB2qdx+LZu9LgoC0Lthuu05DQ==", + "dev": true, + "dependencies": { + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz", + "integrity": "sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", + "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz", + "integrity": "sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.5.tgz", + "integrity": "sha512-6CF8g6z1dNYZ/VXok5uYkkBBICHZPiGEl7oDnAx2Mt1hlHVHOSIKWJaXHjQJA5VB43KZnXZDIexMchY4y2PGdA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.5.tgz", + "integrity": "sha512-NbslED1/6M+sXiwwtcAB/nieypGw02Ejf4KtDeMkCEpP6gWFMX1wI9WKYua+4oBneCCEmulOkRpwywypVZzs/g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.5.tgz", + "integrity": "sha512-Kk3lyDmEslH9DnvCDA1s1kkd3YWQITiBOHngOtDL9Pt6BZjzqb6hiOlb8VfjiiQJ2unmegBqZu0rx5RxJb5vmQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz", + "integrity": "sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.5.tgz", + "integrity": "sha512-pH8orJahy+hzZje5b8e2QIlBWQvGpelS76C63Z+jhZKsmzfNaPQ+LaW6dcJ9bxTpo1mtXbgHwy765Ro3jftmUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.5.tgz", + "integrity": "sha512-AconbMKOMkyG+xCng2JogMCDcqW8wedQAqpVIL4cOSescZ7+iW8utC6YDZLMCSUIReEA733gzRSaOSXMAt/4WQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.5.tgz", + "integrity": "sha512-AVkFUBurORBREOmHRKo06FjHYgjrabpdqRSwq6+C7R5iTCZOsM4QbcB27St0a4U6fffyAOqh3s/qEfybAhfivg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz", + "integrity": "sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.5.tgz", + "integrity": "sha512-/9xnaTTJcVoBtSSmrVyhtSvO3kbqS2ODoh2juEU72c3aYonNF0OMGiaz2gjukyKM2wBBYJP38S4JiE0Wfb5VMQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz", + "integrity": "sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.5.tgz", + "integrity": "sha512-rR7KePOE7gfEtNTh9Qw+iO3Q/e4DEsoQ+hdvM6QUDH7JRJ5qxq5AA52ZzBWbI5i9lfNuvySgOGP8ZN7LAmaiPw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "regenerator-transform": "^0.15.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator/node_modules/regenerator-transform": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", + "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz", + "integrity": "sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz", + "integrity": "sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz", + "integrity": "sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz", + "integrity": "sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz", + "integrity": "sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz", + "integrity": "sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.5.tgz", + "integrity": "sha512-biEmVg1IYB/raUO5wT1tgfacCef15Fbzhkx493D3urBI++6hpJ+RFG4SrWMn0NEZLfvilqKf3QDrRVZHo08FYg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz", + "integrity": "sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz", + "integrity": "sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz", + "integrity": "sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.5.tgz", + "integrity": "sha512-fj06hw89dpiZzGZtxn+QybifF07nNiZjZ7sazs2aVDcysAZVGjW7+7iFYxg6GLNM47R/thYfLdrXc+2f11Vi9A==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.5", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.22.5", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.22.5", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.22.5", + "@babel/plugin-syntax-import-attributes": "^7.22.5", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.22.5", + "@babel/plugin-transform-async-generator-functions": "^7.22.5", + "@babel/plugin-transform-async-to-generator": "^7.22.5", + "@babel/plugin-transform-block-scoped-functions": "^7.22.5", + "@babel/plugin-transform-block-scoping": "^7.22.5", + "@babel/plugin-transform-class-properties": "^7.22.5", + "@babel/plugin-transform-class-static-block": "^7.22.5", + "@babel/plugin-transform-classes": "^7.22.5", + "@babel/plugin-transform-computed-properties": "^7.22.5", + "@babel/plugin-transform-destructuring": "^7.22.5", + "@babel/plugin-transform-dotall-regex": "^7.22.5", + "@babel/plugin-transform-duplicate-keys": "^7.22.5", + "@babel/plugin-transform-dynamic-import": "^7.22.5", + "@babel/plugin-transform-exponentiation-operator": "^7.22.5", + "@babel/plugin-transform-export-namespace-from": "^7.22.5", + "@babel/plugin-transform-for-of": "^7.22.5", + "@babel/plugin-transform-function-name": "^7.22.5", + "@babel/plugin-transform-json-strings": "^7.22.5", + "@babel/plugin-transform-literals": "^7.22.5", + "@babel/plugin-transform-logical-assignment-operators": "^7.22.5", + "@babel/plugin-transform-member-expression-literals": "^7.22.5", + "@babel/plugin-transform-modules-amd": "^7.22.5", + "@babel/plugin-transform-modules-commonjs": "^7.22.5", + "@babel/plugin-transform-modules-systemjs": "^7.22.5", + "@babel/plugin-transform-modules-umd": "^7.22.5", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", + "@babel/plugin-transform-new-target": "^7.22.5", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.5", + "@babel/plugin-transform-numeric-separator": "^7.22.5", + "@babel/plugin-transform-object-rest-spread": "^7.22.5", + "@babel/plugin-transform-object-super": "^7.22.5", + "@babel/plugin-transform-optional-catch-binding": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.22.5", + "@babel/plugin-transform-parameters": "^7.22.5", + "@babel/plugin-transform-private-methods": "^7.22.5", + "@babel/plugin-transform-private-property-in-object": "^7.22.5", + "@babel/plugin-transform-property-literals": "^7.22.5", + "@babel/plugin-transform-regenerator": "^7.22.5", + "@babel/plugin-transform-reserved-words": "^7.22.5", + "@babel/plugin-transform-shorthand-properties": "^7.22.5", + "@babel/plugin-transform-spread": "^7.22.5", + "@babel/plugin-transform-sticky-regex": "^7.22.5", + "@babel/plugin-transform-template-literals": "^7.22.5", + "@babel/plugin-transform-typeof-symbol": "^7.22.5", + "@babel/plugin-transform-unicode-escapes": "^7.22.5", + "@babel/plugin-transform-unicode-property-regex": "^7.22.5", + "@babel/plugin-transform-unicode-regex": "^7.22.5", + "@babel/plugin-transform-unicode-sets-regex": "^7.22.5", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.22.5", + "babel-plugin-polyfill-corejs2": "^0.4.3", + "babel-plugin-polyfill-corejs3": "^0.8.1", + "babel-plugin-polyfill-regenerator": "^0.5.0", + "core-js-compat": "^3.30.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", + "dev": true + }, + "node_modules/@babel/runtime": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz", + "integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.13.11" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime/node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true + }, + "node_modules/@babel/template": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", + "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.5.tgz", + "integrity": "sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/types": "^7.22.5", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/traverse/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@babel/types": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", + "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types/node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", + "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.2.tgz", + "integrity": "sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.5.1", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@eslint/js": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.39.0.tgz", + "integrity": "sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.5.0.tgz", + "integrity": "sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/console/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/console/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/console/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.5.0.tgz", + "integrity": "sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.5.0", + "@jest/reporters": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.5.0", + "jest-config": "^29.5.0", + "jest-haste-map": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-resolve-dependencies": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "jest-watcher": "^29.5.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/core/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core/node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/@jest/core/node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/@jest/core/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core/node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/@jest/environment": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.5.0.tgz", + "integrity": "sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-mock": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.5.0.tgz", + "integrity": "sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g==", + "dev": true, + "dependencies": { + "expect": "^29.5.0", + "jest-snapshot": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.5.0.tgz", + "integrity": "sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.4.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.5.0.tgz", + "integrity": "sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.5.0", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.5.0.tgz", + "integrity": "sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/expect": "^29.5.0", + "@jest/types": "^29.5.0", + "jest-mock": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.5.0.tgz", + "integrity": "sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@jridgewell/trace-mapping": "^0.3.15", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "jest-worker": "^29.5.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/reporters/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/reporters/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/reporters/node_modules/jest-worker": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz", + "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.5.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/@jest/reporters/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/reporters/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/reporters/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/schemas": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", + "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.25.16" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.4.3.tgz", + "integrity": "sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.15", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.5.0.tgz", + "integrity": "sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.5.0.tgz", + "integrity": "sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.5.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/transform": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.5.0.tgz", + "integrity": "sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.5.0", + "@jridgewell/trace-mapping": "^0.3.15", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.5.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/transform/node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/transform/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/transform/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/@jest/transform/node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/transform/node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/@jest/transform/node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/@jest/transform/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/transform/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/transform/node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/@jest/types": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.5.0.tgz", + "integrity": "sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.4.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/types/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/types/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz", + "integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@nicolo-ribaudo/chokidar-2": { + "version": "2.1.8-no-fsevents.3", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz", + "integrity": "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==", + "dev": true, + "optional": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.25.24", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", + "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==", + "dev": true + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.1.tgz", + "integrity": "sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.1.tgz", + "integrity": "sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/eslint": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.37.0.tgz", + "integrity": "sha512-Piet7dG2JBuDIfohBngQ3rCt7MgO9xCO4xIMKxBThCq5PNRB91IjlJ10eJVwfoNtvTErmxLzwBZ7rHZtbOMmFQ==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "dev": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", + "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", + "dev": true + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", + "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.16.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.3.tgz", + "integrity": "sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q==", + "dev": true + }, + "node_modules/@types/prettier": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", + "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", + "dev": true + }, + "node_modules/@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "17.0.24", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", + "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.5.tgz", + "integrity": "sha512-LHY/GSAZZRpsNQH+/oHqhRQ5FT7eoULcBqgfyTB5nQHogFnK3/7QoN7dLnwSE/JkUAF0SrRuclT7ODqMFtWxxQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.5", + "@webassemblyjs/helper-wasm-bytecode": "1.11.5" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.5.tgz", + "integrity": "sha512-1j1zTIC5EZOtCplMBG/IEwLtUojtwFVwdyVMbL/hwWqbzlQoJsWCOavrdnLkemwNoC/EOwtUFch3fuo+cbcXYQ==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.5.tgz", + "integrity": "sha512-L65bDPmfpY0+yFrsgz8b6LhXmbbs38OnwDCf6NpnMUYqa+ENfE5Dq9E42ny0qz/PdR0LJyq/T5YijPnU8AXEpA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.5.tgz", + "integrity": "sha512-fDKo1gstwFFSfacIeH5KfwzjykIE6ldh1iH9Y/8YkAZrhmu4TctqYjSh7t0K2VyDSXOZJ1MLhht/k9IvYGcIxg==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.5.tgz", + "integrity": "sha512-DhykHXM0ZABqfIGYNv93A5KKDw/+ywBFnuWybZZWcuzWHfbp21wUfRkbtz7dMGwGgT4iXjWuhRMA2Mzod6W4WA==", + "dev": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.5", + "@webassemblyjs/helper-api-error": "1.11.5", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.5.tgz", + "integrity": "sha512-oC4Qa0bNcqnjAowFn7MPCETQgDYytpsfvz4ujZz63Zu/a/v71HeCAAmZsgZ3YVKec3zSPYytG3/PrRCqbtcAvA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.5.tgz", + "integrity": "sha512-uEoThA1LN2NA+K3B9wDo3yKlBfVtC6rh0i4/6hvbz071E8gTNZD/pT0MsBf7MeD6KbApMSkaAK0XeKyOZC7CIA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.5", + "@webassemblyjs/helper-buffer": "1.11.5", + "@webassemblyjs/helper-wasm-bytecode": "1.11.5", + "@webassemblyjs/wasm-gen": "1.11.5" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.5.tgz", + "integrity": "sha512-37aGq6qVL8A8oPbPrSGMBcp38YZFXcHfiROflJn9jxSdSMMM5dS5P/9e2/TpaJuhE+wFrbukN2WI6Hw9MH5acg==", + "dev": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.5.tgz", + "integrity": "sha512-ajqrRSXaTJoPW+xmkfYN6l8VIeNnR4vBOTQO9HzR7IygoCcKWkICbKFbVTNMjMgMREqXEr0+2M6zukzM47ZUfQ==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.5.tgz", + "integrity": "sha512-WiOhulHKTZU5UPlRl53gHR8OxdGsSOxqfpqWeA2FmcwBMaoEdz6b2x2si3IwC9/fSPLfe8pBMRTHVMk5nlwnFQ==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.5.tgz", + "integrity": "sha512-C0p9D2fAu3Twwqvygvf42iGCQ4av8MFBLiTb+08SZ4cEdwzWx9QeAHDo1E2k+9s/0w1DM40oflJOpkZ8jW4HCQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.5", + "@webassemblyjs/helper-buffer": "1.11.5", + "@webassemblyjs/helper-wasm-bytecode": "1.11.5", + "@webassemblyjs/helper-wasm-section": "1.11.5", + "@webassemblyjs/wasm-gen": "1.11.5", + "@webassemblyjs/wasm-opt": "1.11.5", + "@webassemblyjs/wasm-parser": "1.11.5", + "@webassemblyjs/wast-printer": "1.11.5" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.5.tgz", + "integrity": "sha512-14vteRlRjxLK9eSyYFvw1K8Vv+iPdZU0Aebk3j6oB8TQiQYuO6hj9s4d7qf6f2HJr2khzvNldAFG13CgdkAIfA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.5", + "@webassemblyjs/helper-wasm-bytecode": "1.11.5", + "@webassemblyjs/ieee754": "1.11.5", + "@webassemblyjs/leb128": "1.11.5", + "@webassemblyjs/utf8": "1.11.5" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.5.tgz", + "integrity": "sha512-tcKwlIXstBQgbKy1MlbDMlXaxpucn42eb17H29rawYLxm5+MsEmgPzeCP8B1Cl69hCice8LeKgZpRUAPtqYPgw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.5", + "@webassemblyjs/helper-buffer": "1.11.5", + "@webassemblyjs/wasm-gen": "1.11.5", + "@webassemblyjs/wasm-parser": "1.11.5" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.5.tgz", + "integrity": "sha512-SVXUIwsLQlc8srSD7jejsfTU83g7pIGr2YYNb9oHdtldSxaOhvA5xwvIiWIfcX8PlSakgqMXsLpLfbbJ4cBYew==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.5", + "@webassemblyjs/helper-api-error": "1.11.5", + "@webassemblyjs/helper-wasm-bytecode": "1.11.5", + "@webassemblyjs/ieee754": "1.11.5", + "@webassemblyjs/leb128": "1.11.5", + "@webassemblyjs/utf8": "1.11.5" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.5.tgz", + "integrity": "sha512-f7Pq3wvg3GSPUPzR0F6bmI89Hdb+u9WXrSKc4v+N0aV0q6r42WoF92Jp2jEorBEBRoRNXgjp53nBniDXcqZYPA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.5", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.0.1.tgz", + "integrity": "sha512-njsdJXJSiS2iNbQVS0eT8A/KPnmyH4pv1APj2K0d1wrZcBLw+yppxOy4CGqa0OxDJkzfL/XELDhD8rocnIwB5A==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.1.tgz", + "integrity": "sha512-fE1UEWTwsAxRhrJNikE7v4EotYflkEhBL7EbajfkPlf6E37/2QshOy/D48Mw8G5XMFlQtS6YV42vtbG9zBpIQA==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.2.tgz", + "integrity": "sha512-S9h3GmOmzUseyeFW3tYNnWS7gNUuwxZ3mmMq0JyW78Vx1SGKPSkt5bT4pB0rUnVfHjP0EL9gW2bOzmtiTfQt0A==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-assertions": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", + "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "dev": true, + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha512-dtXTVMkh6VkEEA7OhXnN1Ecb8aAGFdZ1LFxtOCoqj4qkyOJMt7+qs6Ahdy6p/NQCPYsRSXXivhSB/J5E9jmYKA==", + "dev": true, + "dependencies": { + "arr-flatten": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha512-G2n5bG5fSUCpnsXz4+8FUkYsGPkNfLn9YvS66U5qbTIXI2Ynnlo4Bi42bWv+omKUCqz+ejzfClwne0alJWJPhg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", + "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", + "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha512-XqYMR2dfdGMW+hd0IUZ2PwK+fGeFkOxZJ0wY+JaQAHzt1Zx8LcvpiZD2NiGkEG8qx0CfkAOr5xt76d1e8vG90g==", + "dev": true, + "dependencies": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + } + }, + "node_modules/babel-core": { + "version": "7.0.0-bridge.0", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", + "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==", + "dev": true, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-generator": { + "version": "6.26.1", + "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", + "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", + "dev": true, + "dependencies": { + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "detect-indent": "^4.0.0", + "jsesc": "^1.3.0", + "lodash": "^4.17.4", + "source-map": "^0.5.7", + "trim-right": "^1.0.1" + } + }, + "node_modules/babel-jest": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-23.6.0.tgz", + "integrity": "sha512-lqKGG6LYXYu+DQh/slrQ8nxXQkEkhugdXsU6St7GmhVS7Ilc/22ArwqXNJrf0QaOBjZB0360qZMwXqDYQHXaew==", + "dev": true, + "dependencies": { + "babel-plugin-istanbul": "^4.1.6", + "babel-preset-jest": "^23.2.0" + }, + "peerDependencies": { + "babel-core": "^6.0.0 || ^7.0.0-0" + } + }, + "node_modules/babel-jest/node_modules/babel-plugin-istanbul": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz", + "integrity": "sha512-PWP9FQ1AhZhS01T/4qLSKoHGY/xvkZdVBGlKM/HuxxS3+sC66HhTNR7+MpbO/so/cz/wY94MeSWJuP1hXIPfwQ==", + "dev": true, + "dependencies": { + "babel-plugin-syntax-object-rest-spread": "^6.13.0", + "find-up": "^2.1.0", + "istanbul-lib-instrument": "^1.10.1", + "test-exclude": "^4.2.1" + } + }, + "node_modules/babel-jest/node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "dev": true, + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/babel-jest/node_modules/istanbul-lib-coverage": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz", + "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==", + "dev": true + }, + "node_modules/babel-jest/node_modules/istanbul-lib-instrument": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz", + "integrity": "sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A==", + "dev": true, + "dependencies": { + "babel-generator": "^6.18.0", + "babel-template": "^6.16.0", + "babel-traverse": "^6.18.0", + "babel-types": "^6.18.0", + "babylon": "^6.18.0", + "istanbul-lib-coverage": "^1.2.1", + "semver": "^5.3.0" + } + }, + "node_modules/babel-jest/node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "dev": true, + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/babel-jest/node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/babel-jest/node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dev": true, + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/babel-jest/node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/babel-jest/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/babel-jest/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/babel-jest/node_modules/test-exclude": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-4.2.3.tgz", + "integrity": "sha512-SYbXgY64PT+4GAL2ocI3HwPa4Q4TBKm0cwAVeKOt/Aoc0gSpNRjJX8w0pA1LMKZ3LBmd8pYBqApFNQLII9kavA==", + "dev": true, + "dependencies": { + "arrify": "^1.0.1", + "micromatch": "^2.3.11", + "object-assign": "^4.1.0", + "read-pkg-up": "^1.0.1", + "require-main-filename": "^1.0.1" + } + }, + "node_modules/babel-messages": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha512-Bl3ZiA+LjqaMtNYopA9TYE9HP1tQ+E5dLxE0XrAzcIJeK2UqF0/EaqXwBn9esd4UmTfEab+P+UYQ1GnioFIb/w==", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-23.2.0.tgz", + "integrity": "sha512-N0MlMjZtahXK0yb0K3V9hWPrq5e7tThbghvDr0k3X75UuOOqwsWW6mk8XHD2QvEC0Ca9dLIfTgNU36TeJD6Hnw==", + "dev": true + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.3.tgz", + "integrity": "sha512-bM3gHc337Dta490gg+/AseNB9L4YLHxq1nGKZZSHbhXv4aTYU2MD2cjza1Ru4S6975YLTaL1K8uJf6ukJhhmtw==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.17.7", + "@babel/helper-define-polyfill-provider": "^0.4.0", + "semver": "^6.1.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.1.tgz", + "integrity": "sha512-ikFrZITKg1xH6pLND8zT14UPgjKHiGLqex7rGEZCH2EvhsneJaJPemmpQaIZV5AL03II+lXylw3UmddDK8RU5Q==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.4.0", + "core-js-compat": "^3.30.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.0.tgz", + "integrity": "sha512-hDJtKjMLVa7Z+LwnTCxoDLQj6wdc+B8dun7ayF2fYieI6OzfuvcLMB32ihJZ4UhCBwNYGl5bg/x/P9cMdnkc2g==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.4.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-syntax-object-rest-spread": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", + "integrity": "sha512-C4Aq+GaAj83pRQ0EFgTvw5YO6T3Qz2KGrNRwIj9mSoNHVvdZY4KO2uA6HNtNXCw993iSZnckY1aLW8nOi8i4+w==", + "dev": true + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-23.2.0.tgz", + "integrity": "sha512-AdfWwc0PYvDtwr009yyVNh72Ev68os7SsPmOFVX7zSA+STXuk5CV2iMVazZU01bEoHCSwTkgv4E4HOOcODPkPg==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^23.2.0", + "babel-plugin-syntax-object-rest-spread": "^6.13.0" + } + }, + "node_modules/babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", + "dev": true, + "dependencies": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "node_modules/babel-runtime/node_modules/regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + }, + "node_modules/babel-template": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "integrity": "sha512-PCOcLFW7/eazGUKIoqH97sO9A2UYMahsn/yRQ7uOk37iutwjq7ODtcTNF+iFDSHNfkctqsLRjLP7URnOx0T1fg==", + "dev": true, + "dependencies": { + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" + } + }, + "node_modules/babel-traverse": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha512-iSxeXx7apsjCHe9c7n8VtRXGzI2Bk1rBSOJgCCjfyXb6v1aCqE1KSEpq/8SXuVN8Ka/Rh1WDTF0MDzkvTA4MIA==", + "dev": true, + "dependencies": { + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" + } + }, + "node_modules/babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha512-zhe3V/26rCWsEZK8kZN+HaQj5yQ1CilTObixFzKW1UWjqG7618Twz6YEsCnjfg5gBcJh02DrpCkS9h98ZqDY+g==", + "dev": true, + "dependencies": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "node_modules/babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true, + "bin": { + "babylon": "bin/babylon.js" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha512-xU7bpz2ytJl1bH9cgIurjpg/n8Gohy9GTw81heDYLJQ4RU60dlyJsa+atVF2pI0yMMvKxI9HkKwjePCj5XI1hw==", + "dev": true, + "dependencies": { + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/browserslist": { + "version": "4.21.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", + "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001449", + "electron-to-chromium": "^1.4.284", + "node-releases": "^2.0.8", + "update-browserslist-db": "^1.0.10" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "dev": true, + "dependencies": { + "semver": "^7.0.0" + } + }, + "node_modules/builtins/node_modules/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001482", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001482.tgz", + "integrity": "sha512-F1ZInsg53cegyjroxLNW9DmrEQ1SuGRTO1QlpA0o2/6OpQ0gFeDRoq1yFmnr8Sakn9qwwt9DmbxHB6w167OSuQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "dev": true, + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", + "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", + "dev": true + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/clone-deep/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "dev": true + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "node_modules/core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", + "dev": true, + "hasInstallScript": true + }, + "node_modules/core-js-compat": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.31.0.tgz", + "integrity": "sha512-hM7YCu1cU6Opx7MXNu0NuumM0ezNeAeRKadixyiQELWY3vT3De9S4J5ZBMraWV2vZnrE1Cirl0GtFtDtMUXzPw==", + "dev": true, + "dependencies": { + "browserslist": "^4.21.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dev": true, + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/detect-indent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", + "integrity": "sha512-BDKtmHlOzwI7iRuEkhzsnPoi5ypEhWAJB5RvHWe1kMr06js3uK5B3734i3ui5Yd+wOJV1cpE4JnivPD283GU/A==", + "dev": true, + "dependencies": { + "repeating": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", + "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/docker-names": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/docker-names/-/docker-names-1.2.1.tgz", + "integrity": "sha512-uh42tvWBp10fnMyJ9z0YL9kql+iolxEnQ+pGZANj+gcg/N2NxjrdHbMXT2Y2Y07A8Jf7KJp/6LkElPCfukCdWg==" + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.379", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.379.tgz", + "integrity": "sha512-eRMq6Cf4PhjB14R9U6QcXM/VRQ54Gc3OL9LKnFugUIh2AXm3KJlOizlSfVIgjH76bII4zHGK4t0PVTE5qq8dZg==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/enhanced-resolve": { + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.13.0.tgz", + "integrity": "sha512-eyV8f0y1+bzyfh8xAwW/WTSZpLbjhqc4ne9eGSH4Zo2ejdyiNG9pU6mf9DG8a7+Auk6MFTlNOT4Y2y/9k8GKVg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/envinfo": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", + "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", + "dev": true, + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.21.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", + "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.2.0", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.7", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-module-lexer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.2.1.tgz", + "integrity": "sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==", + "dev": true + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.39.0.tgz", + "integrity": "sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.0.2", + "@eslint/js": "8.39.0", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.0", + "eslint-visitor-keys": "^3.4.0", + "espree": "^9.5.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-standard": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.0.0.tgz", + "integrity": "sha512-/2ks1GKyqSOkH7JFvXJicu0iMpoojkwB+f5Du/1SC0PtBL+s8v30k9njRZ21pm2drKYm2342jFnGWzttxPmZVg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peerDependencies": { + "eslint": "^8.0.1", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-n": "^15.0.0", + "eslint-plugin-promise": "^6.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", + "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.11.0", + "resolve": "^1.22.1" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/eslint-module-utils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", + "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/eslint-plugin-es": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz", + "integrity": "sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==", + "dev": true, + "dependencies": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=4.19.1" + } + }, + "node_modules/eslint-plugin-es/node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-plugin-es/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.27.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", + "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "array.prototype.flatmap": "^1.3.1", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.7", + "eslint-module-utils": "^2.7.4", + "has": "^1.0.3", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.6", + "resolve": "^1.22.1", + "semver": "^6.3.0", + "tsconfig-paths": "^3.14.1" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/eslint-plugin-n": { + "version": "15.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.7.0.tgz", + "integrity": "sha512-jDex9s7D/Qial8AGVIHq4W7NswpUD5DPDL2RH8Lzd9EloWUuvUkHfv4FRLMipH5q2UtyurorBkPeNi1wVWNh3Q==", + "dev": true, + "dependencies": { + "builtins": "^5.0.1", + "eslint-plugin-es": "^4.1.0", + "eslint-utils": "^3.0.0", + "ignore": "^5.1.1", + "is-core-module": "^2.11.0", + "minimatch": "^3.1.2", + "resolve": "^1.22.1", + "semver": "^7.3.8" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-n/node_modules/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-plugin-promise": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", + "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", + "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint/node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/eslint/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz", + "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==", + "dev": true, + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha512-hxx03P2dJxss6ceIeri9cmYOT4SRs3Zk3afZwWpOsRqLqprhTR8u++SlC+sFGsQr7WGFPdMF7Gjc1njDLDK6UA==", + "dev": true, + "dependencies": { + "is-posix-bracket": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha512-AFASGfIlnIbkKPQwX1yHaDjFvh/1gyKJODme52V6IORh69uEYgZp0o9C+qsIGNVEiuuhQU0CSSl++Rlegg1qvA==", + "dev": true, + "dependencies": { + "fill-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.5.0.tgz", + "integrity": "sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha512-1FOj1LOwn42TMrruOHGt18HemVnbwAmAak7krWk+wa93KXxGbK+2jpezm+ytJYDaBX0/SPLZFHKM7m+tKobWGg==", + "dev": true, + "dependencies": { + "is-extglob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha512-BTCqyBaWBTsauvnHiE8i562+EdJj+oUpkqWp2R1iCoR8f6oo8STRu3of7WJJ0TqWtxN50a5YFpzYK4Jj9esYfQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fill-range": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", + "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", + "dev": true, + "dependencies": { + "is-number": "^2.1.0", + "isobject": "^2.0.0", + "randomatic": "^3.0.0", + "repeat-element": "^1.1.2", + "repeat-string": "^1.5.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha512-SKmowqGTJoPzLO1T0BBJpkfp3EMacCMOuH40hOUbrbzElVktk4DioXVM99QkLCyKoiuOmyjgcWMpVz2xjE7LZw==", + "dev": true, + "dependencies": { + "for-in": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fs-readdir-recursive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", + "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha512-ab1S1g1EbO7YzauaJLkgLp7DZVAqj9M/dvKlTt8DkXA2tiOIcSMrlVI2J1RZyB5iJVccEscjGn+kpOG9788MHA==", + "dev": true, + "dependencies": { + "glob-parent": "^2.0.0", + "is-glob": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha512-JDYOvfxio/t42HKdxkAYaCiBN7oYiuxykOxKxdaUW5Qn0zaYN3gRQWolrwdnf0shM9/EP0ebuuTmyoXNr1cC5w==", + "dev": true, + "dependencies": { + "is-glob": "^2.0.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "node_modules/globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz", + "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-dotfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha512-9YclgOGtN/f8zx0Pr4FQYMdibBiTaH3sn52vjYip4ZSf6C4/6RfTEZ+MR4GvKhCxdPh21Bg42/WL55f6KSnKpg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-equal-shallow": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha512-0EygVC5qPvIyb+gSz7zdD5/AAoS6Qrx1e//6N4yv4oNm30kqvdmG66oZFWVlQHUWe5OjP08FuTw2IdT0EOTcYA==", + "dev": true, + "dependencies": { + "is-primitive": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha512-7Q+VbVafe6x2T+Tu6NcOf6sRklazEPmBoB3IWk3WdGZM2iGUwU/Oe3Wtq5lSEkDTTlpp8yx+5t4pzO/i9Ty1ww==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finite": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", + "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", + "dev": true, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha512-a1dBeB19NXsf/E0+FHqkagizel/LQw2DjSQpvQrj3zT+jYPpaUCryPnrQajXKFLCMuf4I6FhRpaGtw4lPrG6Eg==", + "dev": true, + "dependencies": { + "is-extglob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha512-QUzH43Gfb9+5yckcrSA0VBDwEtDUchrk4F6tfJZQuNzDJbEDB9cZNzSfXGQ1jqmdDY/kl41lUOWM9syA8z8jlg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-plain-object/node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha512-Yu68oeXJ7LeWNmZ3Zov/xg/oDBnBK2RNxwYY1ilNJX+tKKZqgPK+qOn/Gs9jEu66KDY9Netf5XLKNGzas/vPfQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha512-N3w1tFaRfk3UrPfqeRyD+GYDASU3W5VinKhlORy8EWVf/sIdDL9GAcew85XmktCfH+ngG7SRXEVDoO18WMdB/Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==", + "dev": true + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", + "dev": true, + "dependencies": { + "isarray": "1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/istanbul-lib-source-maps/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.5.0.tgz", + "integrity": "sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==", + "dev": true, + "dependencies": { + "@jest/core": "^29.5.0", + "@jest/types": "^29.5.0", + "import-local": "^3.0.2", + "jest-cli": "^29.5.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.5.0.tgz", + "integrity": "sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.5.0.tgz", + "integrity": "sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/expect": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.5.0", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.5.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-circus/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-circus/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-circus/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.5.0.tgz", + "integrity": "sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==", + "dev": true, + "dependencies": { + "@jest/core": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "prompts": "^2.0.1", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-cli/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-cli/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-cli/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-config": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.5.0.tgz", + "integrity": "sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.5.0", + "@jest/types": "^29.5.0", + "babel-jest": "^29.5.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.5.0", + "jest-environment-node": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-config/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-config/node_modules/babel-jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.5.0.tgz", + "integrity": "sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q==", + "dev": true, + "dependencies": { + "@jest/transform": "^29.5.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.5.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/jest-config/node_modules/babel-plugin-jest-hoist": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz", + "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-config/node_modules/babel-preset-jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz", + "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.5.0", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/jest-config/node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-config/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-config/node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-config/node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jest-config/node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/jest-config/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-config/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-config/node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/jest-diff": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", + "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.4.3", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-docblock": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz", + "integrity": "sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.5.0.tgz", + "integrity": "sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "jest-util": "^29.5.0", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-each/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-each/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-environment-node": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.5.0.tgz", + "integrity": "sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", + "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.5.0.tgz", + "integrity": "sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.5.0", + "jest-worker": "^29.5.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-haste-map/node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/jest-haste-map/node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-haste-map/node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-haste-map/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/jest-haste-map/node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jest-haste-map/node_modules/jest-worker": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz", + "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.5.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map/node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/jest-haste-map/node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-haste-map/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jest-haste-map/node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz", + "integrity": "sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz", + "integrity": "sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.5.0.tgz", + "integrity": "sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.5.0", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util/node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jest-message-util/node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/jest-message-util/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util/node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/jest-mock": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.5.0.tgz", + "integrity": "sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz", + "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.5.0.tgz", + "integrity": "sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz", + "integrity": "sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg==", + "dev": true, + "dependencies": { + "jest-regex-util": "^29.4.3", + "jest-snapshot": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-resolve/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-resolve/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-resolve/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runner": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.5.0.tgz", + "integrity": "sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.5.0", + "@jest/environment": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.4.3", + "jest-environment-node": "^29.5.0", + "jest-haste-map": "^29.5.0", + "jest-leak-detector": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-resolve": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-util": "^29.5.0", + "jest-watcher": "^29.5.0", + "jest-worker": "^29.5.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-runner/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-runner/node_modules/jest-worker": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz", + "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.5.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner/node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jest-runner/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-runner/node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/jest-runner/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runtime": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.5.0.tgz", + "integrity": "sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/fake-timers": "^29.5.0", + "@jest/globals": "^29.5.0", + "@jest/source-map": "^29.4.3", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-mock": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-runtime/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-runtime/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runtime/node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runtime/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-snapshot": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.5.0.tgz", + "integrity": "sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/babel__traverse": "^7.0.6", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.5.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.5.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-snapshot/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-snapshot/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-util": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.5.0.tgz", + "integrity": "sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-validate": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.5.0.tgz", + "integrity": "sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "leven": "^3.1.0", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-validate/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watcher": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.5.0.tgz", + "integrity": "sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.5.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-watcher/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-watcher/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-watcher/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-sdsl": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", + "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha512-RjTcuD4xjtthQkaWH7dFlH85L+QaVtSoOyGdZ3g6HFhS9dFNDfLyqgm2NFe2X6cQpeFmt0452FJjFG5UameExg==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "integrity": "sha512-Mke0DA0QjUWuJlhsE0ZPPhYiJkRap642SmI/4ztCFaUs6V2AiH1sfecc+57NgaryfAA2VR3v6O+CSjC1jZJKOA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/load-json-file/node_modules/parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", + "dev": true, + "dependencies": { + "error-ex": "^1.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/load-json-file/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/load-json-file/node_modules/strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", + "dev": true, + "dependencies": { + "is-utf8": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/math-random": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", + "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==", + "dev": true + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha512-LnU2XFEk9xxSJ6rfgAry/ty5qwUTyHYOBU0g4R6tIw5ljwgGIBmiKhRWLw5NpMOnrgUNcDJ4WMp8rl3sYVHLNA==", + "dev": true, + "dependencies": { + "arr-diff": "^2.0.0", + "array-unique": "^0.2.1", + "braces": "^1.8.2", + "expand-brackets": "^0.1.4", + "extglob": "^0.3.1", + "filename-regex": "^2.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.1", + "kind-of": "^3.0.2", + "normalize-path": "^2.0.1", + "object.omit": "^2.0.0", + "parse-glob": "^3.0.4", + "regex-cache": "^0.4.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", + "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", + "dev": true + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", + "dev": true, + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha512-UiAM5mhmIuKLsOvrL+B0U2d1hXHF3bFYWIuH1LMpuV2EJEHG1Ntz06PgLEHjm6VFd87NpH8rastvPoyv6UW2fA==", + "dev": true, + "dependencies": { + "for-own": "^0.1.4", + "is-extendable": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.values": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha512-FC5TeK0AwXzq3tUBFtH74naWkPQCEWs4K+xMxWZBlKDWu0bVHXGZa+KKqxKidd7xwhdZ19ZNuF2uO1M/r196HA==", + "dev": true, + "dependencies": { + "glob-base": "^0.3.0", + "is-dotfile": "^1.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-type/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dev": true, + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha512-s/46sYeylUfHNjI+sA/78FAHlmIuKqI9wNnzEOGehAlUUYeObv5C2mOinXBjyUyWmJ2SfcS2/ydApH4hTF4WXQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-format": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", + "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.4.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.2.tgz", + "integrity": "sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/randomatic": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", + "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", + "dev": true, + "dependencies": { + "is-number": "^4.0.0", + "kind-of": "^6.0.0", + "math-random": "^1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/randomatic/node_modules/is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/randomatic/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ==", + "dev": true, + "dependencies": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A==", + "dev": true, + "dependencies": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg-up/node_modules/find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==", + "dev": true, + "dependencies": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg-up/node_modules/path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==", + "dev": true, + "dependencies": { + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", + "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regex-cache": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", + "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "dev": true, + "dependencies": { + "is-equal-shallow": "^0.1.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", + "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==", + "dev": true + }, + "node_modules/repeat-element": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", + "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha512-ZqtSMuVybkISo2OWvqvm7iHSWngvdaW3IpsT9/uP8v4gMi591LY6h35wdOfvQdWCKFWZWm2Y1Opp4kV7vQKT6A==", + "dev": true, + "dependencies": { + "is-finite": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug==", + "dev": true + }, + "node_modules/resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dev": true, + "dependencies": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/schema-utils": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.2.tgz", + "integrity": "sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shallow-clone/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz", + "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==", + "dev": true + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-length/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-length/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", + "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/terser": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.17.1.tgz", + "integrity": "sha512-hVl35zClmpisy6oaoKALOpS0rDYLxRFLHhRuDlEGTKey9qHjS1w9GMORjuwIMt70Wan4lwsLYyWDVnWgF+KUEw==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.7.tgz", + "integrity": "sha512-AfKwIktyP7Cu50xNjXF/6Qb5lBNzYaWpU6YfoX3uZicTx0zTy0stDDCsvjDapKsSDvOeWo5MEq4TmdBy2cNoHw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.17", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.16.5" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/terser/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha512-lxrWP8ejsq+7E3nNjwYmUBMAgjMTZoTI+sdBOpvNyijeDLa29LUn9QaoXAHv4+Z578hbmHHJKZknzxVtvo77og==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha512-WZGXGstmCWgeevgTL54hrCuw1dyMQIzWy7ZfqRJfSmJZBwklI15egmQytFP6bPidmw3M8d5yEowl1niq4vmqZw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", + "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", + "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack": { + "version": "5.81.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.81.0.tgz", + "integrity": "sha512-AAjaJ9S4hYCVODKLQTgG5p5e11hiMawBwV2v8MYLE0C/6UAGLuAF4n1qa9GOwdxnicaP+5k6M5HrLmD4+gIB8Q==", + "dev": true, + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.0", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.7.6", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.13.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.1.2", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.7", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-cli": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.0.2.tgz", + "integrity": "sha512-4y3W5Dawri5+8dXm3+diW6Mn1Ya+Dei6eEVAdIduAmYNLzv1koKVAqsfgrrc9P2mhrYHQphx5htnGkcNwtubyQ==", + "dev": true, + "dependencies": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^2.0.1", + "@webpack-cli/info": "^2.0.1", + "@webpack-cli/serve": "^2.0.2", + "colorette": "^2.0.14", + "commander": "^10.0.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^5.7.3" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/webpack-merge": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", + "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/webpack/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@babel/cli": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.22.5.tgz", + "integrity": "sha512-N5d7MjzwsQ2wppwjhrsicVDhJSqF9labEP/swYiHhio4Ca2XjEehpgPmerjnLQl7BPE59BLud0PTWGYwqFl/cQ==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.17", + "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3", + "chokidar": "^3.4.0", + "commander": "^4.0.1", + "convert-source-map": "^1.1.0", + "fs-readdir-recursive": "^1.1.0", + "glob": "^7.2.0", + "make-dir": "^2.1.0", + "slash": "^2.0.0" + }, + "dependencies": { + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "optional": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "optional": true + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "optional": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "optional": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "optional": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "optional": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "optional": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "optional": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "optional": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "optional": true + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "optional": true + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "optional": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "optional": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, + "@babel/code-frame": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", + "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", + "dev": true, + "requires": { + "@babel/highlight": "^7.22.5" + } + }, + "@babel/compat-data": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.5.tgz", + "integrity": "sha512-4Jc/YuIaYqKnDDz892kPIledykKg12Aw1PYX5i/TY28anJtacvM1Rrr8wbieB9GfEJwlzqT0hUEao0CxEebiDA==", + "dev": true + }, + "@babel/core": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.5.tgz", + "integrity": "sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helpers": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.0" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.5.tgz", + "integrity": "sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "dependencies": { + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + } + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.5.tgz", + "integrity": "sha512-m1EP3lVOPptR+2DwD125gziZNcmoNSHGmJROKoy87loWUQyJaVXDgpmruWqDARZSmtYQ+Dl25okU8+qhVzuykw==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.5.tgz", + "integrity": "sha512-Ji+ywpHeuqxB8WDxraCiqR0xfhYjiDE/e6k7FuIaANnoOFxAHskHChz4vA1mJC9Lbm01s1PVAGhQY4FUKSkGZw==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.22.5", + "@babel/helper-validator-option": "^7.22.5", + "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", + "semver": "^6.3.0" + }, + "dependencies": { + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.5.tgz", + "integrity": "sha512-xkb58MyOYIslxu3gKmVXmjTtUPvBU4odYzbiIQbWwLKIHCsx6UGZGX6F1IznMFVnDdirseUZopzN+ZRt8Xb33Q==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-member-expression-to-functions": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.5", + "semver": "^6.3.0" + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.5.tgz", + "integrity": "sha512-1VpEFOIbMRaXyDeUwUfmTIxExLwQ+zkW+Bh5zXpApA3oQedBx9v/updixWxnx/bZpKw7u8VxWjb/qWpIcmPq8A==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "regexpu-core": "^5.3.1", + "semver": "^6.3.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true + }, + "regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "dev": true, + "requires": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + } + }, + "regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + } + } + } + }, + "@babel/helper-define-polyfill-provider": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.0.tgz", + "integrity": "sha512-RnanLx5ETe6aybRi1cO/edaRH+bNYWaryCEmjDDYyNr4wnSzyOp8T0dWipmqVHKEY3AbVKUom50AKSlj1zmKbg==", + "dev": true, + "requires": { + "@babel/helper-compilation-targets": "^7.17.7", + "@babel/helper-plugin-utils": "^7.16.7", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@babel/helper-environment-visitor": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", + "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", + "dev": true + }, + "@babel/helper-function-name": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", + "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "dev": true, + "requires": { + "@babel/template": "^7.22.5", + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz", + "integrity": "sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-module-imports": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", + "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-module-transforms": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.5.tgz", + "integrity": "sha512-+hGKDt/Ze8GFExiVHno/2dvG5IdstpzCq0y4Qc9OJ25D4q3pKfiIP/4Vp3/JvhDkLKsDK2api3q3fpIgiIF5bw==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "dev": true + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.5.tgz", + "integrity": "sha512-cU0Sq1Rf4Z55fgz7haOakIyM7+x/uCFwXpLPaeRzfoUtAEAuUZjZvFPjL/rk5rW693dIgn2hng1W7xbT7lWT4g==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-wrap-function": "^7.22.5", + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-replace-supers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.5.tgz", + "integrity": "sha512-aLdNM5I3kdI/V9xGNyKSF3X/gTyMUBohTZ+/3QdQKAA9vxIiy12E+8E2HoOP1/DjeqU+g6as35QHJNMDDYpuCg==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-member-expression-to-functions": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", + "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz", + "integrity": "sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-string-parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "dev": true + }, + "@babel/helper-validator-identifier": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", + "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", + "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", + "dev": true + }, + "@babel/helper-wrap-function": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.5.tgz", + "integrity": "sha512-bYqLIBSEshYcYQyfks8ewYA8S30yaGSeRslcvKMvoUk6HHPySbxHq9YRi6ghhzEU+yhQv9bP/jXnygkStOcqZw==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5" + } + }, + "@babel/helpers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.5.tgz", + "integrity": "sha512-pSXRmfE1vzcUIDFQcSGA5Mr+GxBV9oiRKDuDxXvWQQBCh8HoIjs/2DlDB7H8smac1IVrB9/xdXj2N3Wol9Cr+Q==", + "dev": true, + "requires": { + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5" + } + }, + "@babel/highlight": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", + "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.22.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz", + "integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==", + "dev": true + }, + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.5.tgz", + "integrity": "sha512-NP1M5Rf+u2Gw9qfSO4ihjcTGW5zXTi36ITLd4/EoAcEhIZ0yjMqmftDNl3QC19CX7olhrjpyU454g/2W7X0jvQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.5.tgz", + "integrity": "sha512-31Bb65aZaUwqCbWMnZPduIZxCBngHFlzyN6Dq6KAJjtx+lx6ohKHubc61OomYi7XwVD4Ol0XCVz4h+pYFR048g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.22.5" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", + "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.20.7" + } + }, + "@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "requires": {} + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", + "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-syntax-import-assertions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz", + "integrity": "sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-syntax-import-attributes": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz", + "integrity": "sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-jsx": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", + "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-typescript": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", + "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz", + "integrity": "sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-async-generator-functions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.5.tgz", + "integrity": "sha512-gGOEvFzm3fWoyD5uZq7vVTD57pPJ3PczPUD/xCFGjzBpUosnklmXyKnGQbbbGs1NPNPskFex0j93yKbHt0cHyg==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.5", + "@babel/plugin-syntax-async-generators": "^7.8.4" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz", + "integrity": "sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.5" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz", + "integrity": "sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.5.tgz", + "integrity": "sha512-EcACl1i5fSQ6bt+YGuU/XGCeZKStLmyVGytWkpyhCLeQVA0eu6Wtiw92V+I1T/hnezUv7j74dA/Ro69gWcU+hg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-class-properties": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz", + "integrity": "sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-class-static-block": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.5.tgz", + "integrity": "sha512-SPToJ5eYZLxlnp1UzdARpOGeC2GbHvr9d/UV0EukuVx8atktg194oe+C5BqQ8jRTkgLRVOPYeXRSBg1IlMoVRA==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.5.tgz", + "integrity": "sha512-2edQhLfibpWpsVBx2n/GKOz6JdGQvLruZQfGr9l1qes2KQaWswjBzhQF7UDUZMNaMMQeYnQzxwOMPsbYF7wqPQ==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.5", + "globals": "^11.1.0" + }, + "dependencies": { + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + } + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz", + "integrity": "sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/template": "^7.22.5" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.5.tgz", + "integrity": "sha512-GfqcFuGW8vnEqTUBM7UtPd5A4q797LTvvwKxXTgRsFjoqaJiEg9deBG6kWeQYkVEL569NpnmpC0Pkr/8BLKGnQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz", + "integrity": "sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz", + "integrity": "sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-dynamic-import": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.5.tgz", + "integrity": "sha512-0MC3ppTB1AMxd8fXjSrbPa7LT9hrImt+/fcj+Pg5YMD7UQyWp/02+JWpdnCymmsXwIx5Z+sYn1bwCn4ZJNvhqQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz", + "integrity": "sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g==", + "dev": true, + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-export-namespace-from": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.5.tgz", + "integrity": "sha512-X4hhm7FRnPgd4nDA4b/5V280xCx6oL7Oob5+9qVS5C13Zq4bh1qq7LU0GgRU6b5dBWBvhGaXYVB4AcN6+ol6vg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.5.tgz", + "integrity": "sha512-3kxQjX1dU9uudwSshyLeEipvrLjBCVthCgeTp6CzE/9JYrlAIaeekVxRpCWsDDfYTfRZRoCeZatCQvwo+wvK8A==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz", + "integrity": "sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg==", + "dev": true, + "requires": { + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-json-strings": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.5.tgz", + "integrity": "sha512-DuCRB7fu8MyTLbEQd1ew3R85nx/88yMoqo2uPSjevMj3yoN7CDM8jkgrY0wmVxfJZyJ/B9fE1iq7EQppWQmR5A==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-json-strings": "^7.8.3" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz", + "integrity": "sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-logical-assignment-operators": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.5.tgz", + "integrity": "sha512-MQQOUW1KL8X0cDWfbwYP+TbVbZm16QmQXJQ+vndPtH/BoO0lOKpVoEDMI7+PskYxH+IiE0tS8xZye0qr1lGzSA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz", + "integrity": "sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.22.5.tgz", + "integrity": "sha512-R+PTfLTcYEmb1+kK7FNkhQ1gP4KgjpSO6HfH9+f8/yfp2Nt3ggBjiVpRwmwTlfqZLafYKJACy36yDXlEmI9HjQ==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.5.tgz", + "integrity": "sha512-B4pzOXj+ONRmuaQTg05b3y/4DuFz3WcCNAXPLb2Q0GT0TrGKGxNKV4jwsXts+StaM0LQczZbOpj8o1DLPDJIiA==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.5.tgz", + "integrity": "sha512-emtEpoaTMsOs6Tzz+nbmcePl6AKVtS1yC4YNAeMun9U8YCsgadPNxnOPQ8GhHFB2qdx+LZu9LgoC0Lthuu05DQ==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz", + "integrity": "sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", + "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz", + "integrity": "sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.5.tgz", + "integrity": "sha512-6CF8g6z1dNYZ/VXok5uYkkBBICHZPiGEl7oDnAx2Mt1hlHVHOSIKWJaXHjQJA5VB43KZnXZDIexMchY4y2PGdA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + } + }, + "@babel/plugin-transform-numeric-separator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.5.tgz", + "integrity": "sha512-NbslED1/6M+sXiwwtcAB/nieypGw02Ejf4KtDeMkCEpP6gWFMX1wI9WKYua+4oBneCCEmulOkRpwywypVZzs/g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + } + }, + "@babel/plugin-transform-object-rest-spread": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.5.tgz", + "integrity": "sha512-Kk3lyDmEslH9DnvCDA1s1kkd3YWQITiBOHngOtDL9Pt6BZjzqb6hiOlb8VfjiiQJ2unmegBqZu0rx5RxJb5vmQ==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.22.5" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz", + "integrity": "sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.5" + } + }, + "@babel/plugin-transform-optional-catch-binding": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.5.tgz", + "integrity": "sha512-pH8orJahy+hzZje5b8e2QIlBWQvGpelS76C63Z+jhZKsmzfNaPQ+LaW6dcJ9bxTpo1mtXbgHwy765Ro3jftmUg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + } + }, + "@babel/plugin-transform-optional-chaining": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.5.tgz", + "integrity": "sha512-AconbMKOMkyG+xCng2JogMCDcqW8wedQAqpVIL4cOSescZ7+iW8utC6YDZLMCSUIReEA733gzRSaOSXMAt/4WQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.5.tgz", + "integrity": "sha512-AVkFUBurORBREOmHRKo06FjHYgjrabpdqRSwq6+C7R5iTCZOsM4QbcB27St0a4U6fffyAOqh3s/qEfybAhfivg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-private-methods": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz", + "integrity": "sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-private-property-in-object": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.5.tgz", + "integrity": "sha512-/9xnaTTJcVoBtSSmrVyhtSvO3kbqS2ODoh2juEU72c3aYonNF0OMGiaz2gjukyKM2wBBYJP38S4JiE0Wfb5VMQ==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz", + "integrity": "sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.5.tgz", + "integrity": "sha512-rR7KePOE7gfEtNTh9Qw+iO3Q/e4DEsoQ+hdvM6QUDH7JRJ5qxq5AA52ZzBWbI5i9lfNuvySgOGP8ZN7LAmaiPw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "regenerator-transform": "^0.15.1" + }, + "dependencies": { + "regenerator-transform": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", + "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.8.4" + } + } + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz", + "integrity": "sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz", + "integrity": "sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz", + "integrity": "sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz", + "integrity": "sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz", + "integrity": "sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz", + "integrity": "sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-unicode-escapes": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.5.tgz", + "integrity": "sha512-biEmVg1IYB/raUO5wT1tgfacCef15Fbzhkx493D3urBI++6hpJ+RFG4SrWMn0NEZLfvilqKf3QDrRVZHo08FYg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-unicode-property-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz", + "integrity": "sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz", + "integrity": "sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-unicode-sets-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz", + "integrity": "sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/preset-env": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.5.tgz", + "integrity": "sha512-fj06hw89dpiZzGZtxn+QybifF07nNiZjZ7sazs2aVDcysAZVGjW7+7iFYxg6GLNM47R/thYfLdrXc+2f11Vi9A==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.5", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.22.5", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.22.5", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.22.5", + "@babel/plugin-syntax-import-attributes": "^7.22.5", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.22.5", + "@babel/plugin-transform-async-generator-functions": "^7.22.5", + "@babel/plugin-transform-async-to-generator": "^7.22.5", + "@babel/plugin-transform-block-scoped-functions": "^7.22.5", + "@babel/plugin-transform-block-scoping": "^7.22.5", + "@babel/plugin-transform-class-properties": "^7.22.5", + "@babel/plugin-transform-class-static-block": "^7.22.5", + "@babel/plugin-transform-classes": "^7.22.5", + "@babel/plugin-transform-computed-properties": "^7.22.5", + "@babel/plugin-transform-destructuring": "^7.22.5", + "@babel/plugin-transform-dotall-regex": "^7.22.5", + "@babel/plugin-transform-duplicate-keys": "^7.22.5", + "@babel/plugin-transform-dynamic-import": "^7.22.5", + "@babel/plugin-transform-exponentiation-operator": "^7.22.5", + "@babel/plugin-transform-export-namespace-from": "^7.22.5", + "@babel/plugin-transform-for-of": "^7.22.5", + "@babel/plugin-transform-function-name": "^7.22.5", + "@babel/plugin-transform-json-strings": "^7.22.5", + "@babel/plugin-transform-literals": "^7.22.5", + "@babel/plugin-transform-logical-assignment-operators": "^7.22.5", + "@babel/plugin-transform-member-expression-literals": "^7.22.5", + "@babel/plugin-transform-modules-amd": "^7.22.5", + "@babel/plugin-transform-modules-commonjs": "^7.22.5", + "@babel/plugin-transform-modules-systemjs": "^7.22.5", + "@babel/plugin-transform-modules-umd": "^7.22.5", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", + "@babel/plugin-transform-new-target": "^7.22.5", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.5", + "@babel/plugin-transform-numeric-separator": "^7.22.5", + "@babel/plugin-transform-object-rest-spread": "^7.22.5", + "@babel/plugin-transform-object-super": "^7.22.5", + "@babel/plugin-transform-optional-catch-binding": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.22.5", + "@babel/plugin-transform-parameters": "^7.22.5", + "@babel/plugin-transform-private-methods": "^7.22.5", + "@babel/plugin-transform-private-property-in-object": "^7.22.5", + "@babel/plugin-transform-property-literals": "^7.22.5", + "@babel/plugin-transform-regenerator": "^7.22.5", + "@babel/plugin-transform-reserved-words": "^7.22.5", + "@babel/plugin-transform-shorthand-properties": "^7.22.5", + "@babel/plugin-transform-spread": "^7.22.5", + "@babel/plugin-transform-sticky-regex": "^7.22.5", + "@babel/plugin-transform-template-literals": "^7.22.5", + "@babel/plugin-transform-typeof-symbol": "^7.22.5", + "@babel/plugin-transform-unicode-escapes": "^7.22.5", + "@babel/plugin-transform-unicode-property-regex": "^7.22.5", + "@babel/plugin-transform-unicode-regex": "^7.22.5", + "@babel/plugin-transform-unicode-sets-regex": "^7.22.5", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.22.5", + "babel-plugin-polyfill-corejs2": "^0.4.3", + "babel-plugin-polyfill-corejs3": "^0.8.1", + "babel-plugin-polyfill-regenerator": "^0.5.0", + "core-js-compat": "^3.30.2", + "semver": "^6.3.0" + } + }, + "@babel/preset-modules": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + } + }, + "@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", + "dev": true + }, + "@babel/runtime": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz", + "integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.11" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true + } + } + }, + "@babel/template": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", + "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/types": "^7.22.5" + } + }, + "@babel/traverse": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.5.tgz", + "integrity": "sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/types": "^7.22.5", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@babel/types": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", + "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5", + "to-fast-properties": "^2.0.0" + }, + "dependencies": { + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true + } + } + }, + "@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true + }, + "@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^3.3.0" + } + }, + "@eslint-community/regexpp": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", + "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", + "dev": true + }, + "@eslint/eslintrc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.2.tgz", + "integrity": "sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.5.1", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@eslint/js": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.39.0.tgz", + "integrity": "sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng==", + "dev": true + }, + "@humanwhocodes/config-array": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + } + } + }, + "@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true + }, + "@jest/console": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.5.0.tgz", + "integrity": "sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ==", + "dev": true, + "requires": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "slash": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jest/core": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.5.0.tgz", + "integrity": "sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==", + "dev": true, + "requires": { + "@jest/console": "^29.5.0", + "@jest/reporters": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.5.0", + "jest-config": "^29.5.0", + "jest-haste-map": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-resolve-dependencies": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "jest-watcher": "^29.5.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, + "@jest/environment": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.5.0.tgz", + "integrity": "sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ==", + "dev": true, + "requires": { + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-mock": "^29.5.0" + } + }, + "@jest/expect": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.5.0.tgz", + "integrity": "sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g==", + "dev": true, + "requires": { + "expect": "^29.5.0", + "jest-snapshot": "^29.5.0" + } + }, + "@jest/expect-utils": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.5.0.tgz", + "integrity": "sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg==", + "dev": true, + "requires": { + "jest-get-type": "^29.4.3" + } + }, + "@jest/fake-timers": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.5.0.tgz", + "integrity": "sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg==", + "dev": true, + "requires": { + "@jest/types": "^29.5.0", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.5.0", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" + } + }, + "@jest/globals": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.5.0.tgz", + "integrity": "sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ==", + "dev": true, + "requires": { + "@jest/environment": "^29.5.0", + "@jest/expect": "^29.5.0", + "@jest/types": "^29.5.0", + "jest-mock": "^29.5.0" + } + }, + "@jest/reporters": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.5.0.tgz", + "integrity": "sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA==", + "dev": true, + "requires": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@jridgewell/trace-mapping": "^0.3.15", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "jest-worker": "^29.5.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "jest-worker": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz", + "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==", + "dev": true, + "requires": { + "@types/node": "*", + "jest-util": "^29.5.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jest/schemas": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", + "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.25.16" + } + }, + "@jest/source-map": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.4.3.tgz", + "integrity": "sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.15", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + } + }, + "@jest/test-result": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.5.0.tgz", + "integrity": "sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ==", + "dev": true, + "requires": { + "@jest/console": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + } + }, + "@jest/test-sequencer": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.5.0.tgz", + "integrity": "sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ==", + "dev": true, + "requires": { + "@jest/test-result": "^29.5.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "slash": "^3.0.0" + }, + "dependencies": { + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + } + } + }, + "@jest/transform": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.5.0.tgz", + "integrity": "sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.5.0", + "@jridgewell/trace-mapping": "^0.3.15", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.5.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, + "@jest/types": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.5.0.tgz", + "integrity": "sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==", + "dev": true, + "requires": { + "@jest/schemas": "^29.4.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true + }, + "@jridgewell/source-map": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz", + "integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "@nicolo-ribaudo/chokidar-2": { + "version": "2.1.8-no-fsevents.3", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz", + "integrity": "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==", + "dev": true, + "optional": true + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@sinclair/typebox": { + "version": "0.25.24", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", + "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==", + "dev": true + }, + "@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^3.0.0" + } + }, + "@types/babel__core": { + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.1.tgz", + "integrity": "sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==", + "dev": true, + "requires": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.1.tgz", + "integrity": "sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg==", + "dev": true, + "requires": { + "@babel/types": "^7.20.7" + } + }, + "@types/eslint": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.37.0.tgz", + "integrity": "sha512-Piet7dG2JBuDIfohBngQ3rCt7MgO9xCO4xIMKxBThCq5PNRB91IjlJ10eJVwfoNtvTErmxLzwBZ7rHZtbOMmFQ==", + "dev": true, + "requires": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "@types/eslint-scope": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "dev": true, + "requires": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "@types/estree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", + "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", + "dev": true + }, + "@types/graceful-fs": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", + "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "@types/node": { + "version": "18.16.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.3.tgz", + "integrity": "sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q==", + "dev": true + }, + "@types/prettier": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", + "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", + "dev": true + }, + "@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "@types/yargs": { + "version": "17.0.24", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", + "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "@webassemblyjs/ast": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.5.tgz", + "integrity": "sha512-LHY/GSAZZRpsNQH+/oHqhRQ5FT7eoULcBqgfyTB5nQHogFnK3/7QoN7dLnwSE/JkUAF0SrRuclT7ODqMFtWxxQ==", + "dev": true, + "requires": { + "@webassemblyjs/helper-numbers": "1.11.5", + "@webassemblyjs/helper-wasm-bytecode": "1.11.5" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.5.tgz", + "integrity": "sha512-1j1zTIC5EZOtCplMBG/IEwLtUojtwFVwdyVMbL/hwWqbzlQoJsWCOavrdnLkemwNoC/EOwtUFch3fuo+cbcXYQ==", + "dev": true + }, + "@webassemblyjs/helper-api-error": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.5.tgz", + "integrity": "sha512-L65bDPmfpY0+yFrsgz8b6LhXmbbs38OnwDCf6NpnMUYqa+ENfE5Dq9E42ny0qz/PdR0LJyq/T5YijPnU8AXEpA==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.5.tgz", + "integrity": "sha512-fDKo1gstwFFSfacIeH5KfwzjykIE6ldh1iH9Y/8YkAZrhmu4TctqYjSh7t0K2VyDSXOZJ1MLhht/k9IvYGcIxg==", + "dev": true + }, + "@webassemblyjs/helper-numbers": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.5.tgz", + "integrity": "sha512-DhykHXM0ZABqfIGYNv93A5KKDw/+ywBFnuWybZZWcuzWHfbp21wUfRkbtz7dMGwGgT4iXjWuhRMA2Mzod6W4WA==", + "dev": true, + "requires": { + "@webassemblyjs/floating-point-hex-parser": "1.11.5", + "@webassemblyjs/helper-api-error": "1.11.5", + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.5.tgz", + "integrity": "sha512-oC4Qa0bNcqnjAowFn7MPCETQgDYytpsfvz4ujZz63Zu/a/v71HeCAAmZsgZ3YVKec3zSPYytG3/PrRCqbtcAvA==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.5.tgz", + "integrity": "sha512-uEoThA1LN2NA+K3B9wDo3yKlBfVtC6rh0i4/6hvbz071E8gTNZD/pT0MsBf7MeD6KbApMSkaAK0XeKyOZC7CIA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.5", + "@webassemblyjs/helper-buffer": "1.11.5", + "@webassemblyjs/helper-wasm-bytecode": "1.11.5", + "@webassemblyjs/wasm-gen": "1.11.5" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.5.tgz", + "integrity": "sha512-37aGq6qVL8A8oPbPrSGMBcp38YZFXcHfiROflJn9jxSdSMMM5dS5P/9e2/TpaJuhE+wFrbukN2WI6Hw9MH5acg==", + "dev": true, + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.5.tgz", + "integrity": "sha512-ajqrRSXaTJoPW+xmkfYN6l8VIeNnR4vBOTQO9HzR7IygoCcKWkICbKFbVTNMjMgMREqXEr0+2M6zukzM47ZUfQ==", + "dev": true, + "requires": { + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/utf8": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.5.tgz", + "integrity": "sha512-WiOhulHKTZU5UPlRl53gHR8OxdGsSOxqfpqWeA2FmcwBMaoEdz6b2x2si3IwC9/fSPLfe8pBMRTHVMk5nlwnFQ==", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.5.tgz", + "integrity": "sha512-C0p9D2fAu3Twwqvygvf42iGCQ4av8MFBLiTb+08SZ4cEdwzWx9QeAHDo1E2k+9s/0w1DM40oflJOpkZ8jW4HCQ==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.5", + "@webassemblyjs/helper-buffer": "1.11.5", + "@webassemblyjs/helper-wasm-bytecode": "1.11.5", + "@webassemblyjs/helper-wasm-section": "1.11.5", + "@webassemblyjs/wasm-gen": "1.11.5", + "@webassemblyjs/wasm-opt": "1.11.5", + "@webassemblyjs/wasm-parser": "1.11.5", + "@webassemblyjs/wast-printer": "1.11.5" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.5.tgz", + "integrity": "sha512-14vteRlRjxLK9eSyYFvw1K8Vv+iPdZU0Aebk3j6oB8TQiQYuO6hj9s4d7qf6f2HJr2khzvNldAFG13CgdkAIfA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.5", + "@webassemblyjs/helper-wasm-bytecode": "1.11.5", + "@webassemblyjs/ieee754": "1.11.5", + "@webassemblyjs/leb128": "1.11.5", + "@webassemblyjs/utf8": "1.11.5" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.5.tgz", + "integrity": "sha512-tcKwlIXstBQgbKy1MlbDMlXaxpucn42eb17H29rawYLxm5+MsEmgPzeCP8B1Cl69hCice8LeKgZpRUAPtqYPgw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.5", + "@webassemblyjs/helper-buffer": "1.11.5", + "@webassemblyjs/wasm-gen": "1.11.5", + "@webassemblyjs/wasm-parser": "1.11.5" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.5.tgz", + "integrity": "sha512-SVXUIwsLQlc8srSD7jejsfTU83g7pIGr2YYNb9oHdtldSxaOhvA5xwvIiWIfcX8PlSakgqMXsLpLfbbJ4cBYew==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.5", + "@webassemblyjs/helper-api-error": "1.11.5", + "@webassemblyjs/helper-wasm-bytecode": "1.11.5", + "@webassemblyjs/ieee754": "1.11.5", + "@webassemblyjs/leb128": "1.11.5", + "@webassemblyjs/utf8": "1.11.5" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.5.tgz", + "integrity": "sha512-f7Pq3wvg3GSPUPzR0F6bmI89Hdb+u9WXrSKc4v+N0aV0q6r42WoF92Jp2jEorBEBRoRNXgjp53nBniDXcqZYPA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.5", + "@xtuc/long": "4.2.2" + } + }, + "@webpack-cli/configtest": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.0.1.tgz", + "integrity": "sha512-njsdJXJSiS2iNbQVS0eT8A/KPnmyH4pv1APj2K0d1wrZcBLw+yppxOy4CGqa0OxDJkzfL/XELDhD8rocnIwB5A==", + "dev": true, + "requires": {} + }, + "@webpack-cli/info": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.1.tgz", + "integrity": "sha512-fE1UEWTwsAxRhrJNikE7v4EotYflkEhBL7EbajfkPlf6E37/2QshOy/D48Mw8G5XMFlQtS6YV42vtbG9zBpIQA==", + "dev": true, + "requires": {} + }, + "@webpack-cli/serve": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.2.tgz", + "integrity": "sha512-S9h3GmOmzUseyeFW3tYNnWS7gNUuwxZ3mmMq0JyW78Vx1SGKPSkt5bT4pB0rUnVfHjP0EL9gW2bOzmtiTfQt0A==", + "dev": true, + "requires": {} + }, + "@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true + }, + "acorn-import-assertions": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", + "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "dev": true, + "requires": {} + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "requires": {} + }, + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + }, + "dependencies": { + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true + } + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "dev": true + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha512-dtXTVMkh6VkEEA7OhXnN1Ecb8aAGFdZ1LFxtOCoqj4qkyOJMt7+qs6Ahdy6p/NQCPYsRSXXivhSB/J5E9jmYKA==", + "dev": true, + "requires": { + "arr-flatten": "^1.0.1" + } + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + } + }, + "array-includes": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" + } + }, + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha512-G2n5bG5fSUCpnsXz4+8FUkYsGPkNfLn9YvS66U5qbTIXI2Ynnlo4Bi42bWv+omKUCqz+ejzfClwne0alJWJPhg==", + "dev": true + }, + "array.prototype.flat": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", + "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + } + }, + "array.prototype.flatmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", + "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + } + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "dev": true + }, + "available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha512-XqYMR2dfdGMW+hd0IUZ2PwK+fGeFkOxZJ0wY+JaQAHzt1Zx8LcvpiZD2NiGkEG8qx0CfkAOr5xt76d1e8vG90g==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + } + }, + "babel-core": { + "version": "7.0.0-bridge.0", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", + "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==", + "dev": true, + "requires": {} + }, + "babel-generator": { + "version": "6.26.1", + "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", + "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", + "dev": true, + "requires": { + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "detect-indent": "^4.0.0", + "jsesc": "^1.3.0", + "lodash": "^4.17.4", + "source-map": "^0.5.7", + "trim-right": "^1.0.1" + } + }, + "babel-jest": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-23.6.0.tgz", + "integrity": "sha512-lqKGG6LYXYu+DQh/slrQ8nxXQkEkhugdXsU6St7GmhVS7Ilc/22ArwqXNJrf0QaOBjZB0360qZMwXqDYQHXaew==", + "dev": true, + "requires": { + "babel-plugin-istanbul": "^4.1.6", + "babel-preset-jest": "^23.2.0" + }, + "dependencies": { + "babel-plugin-istanbul": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz", + "integrity": "sha512-PWP9FQ1AhZhS01T/4qLSKoHGY/xvkZdVBGlKM/HuxxS3+sC66HhTNR7+MpbO/so/cz/wY94MeSWJuP1hXIPfwQ==", + "dev": true, + "requires": { + "babel-plugin-syntax-object-rest-spread": "^6.13.0", + "find-up": "^2.1.0", + "istanbul-lib-instrument": "^1.10.1", + "test-exclude": "^4.2.1" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "istanbul-lib-coverage": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz", + "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz", + "integrity": "sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A==", + "dev": true, + "requires": { + "babel-generator": "^6.18.0", + "babel-template": "^6.16.0", + "babel-traverse": "^6.18.0", + "babel-types": "^6.18.0", + "babylon": "^6.18.0", + "istanbul-lib-coverage": "^1.2.1", + "semver": "^5.3.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "test-exclude": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-4.2.3.tgz", + "integrity": "sha512-SYbXgY64PT+4GAL2ocI3HwPa4Q4TBKm0cwAVeKOt/Aoc0gSpNRjJX8w0pA1LMKZ3LBmd8pYBqApFNQLII9kavA==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "micromatch": "^2.3.11", + "object-assign": "^4.1.0", + "read-pkg-up": "^1.0.1", + "require-main-filename": "^1.0.1" + } + } + } + }, + "babel-messages": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha512-Bl3ZiA+LjqaMtNYopA9TYE9HP1tQ+E5dLxE0XrAzcIJeK2UqF0/EaqXwBn9esd4UmTfEab+P+UYQ1GnioFIb/w==", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + } + }, + "babel-plugin-jest-hoist": { + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-23.2.0.tgz", + "integrity": "sha512-N0MlMjZtahXK0yb0K3V9hWPrq5e7tThbghvDr0k3X75UuOOqwsWW6mk8XHD2QvEC0Ca9dLIfTgNU36TeJD6Hnw==", + "dev": true + }, + "babel-plugin-polyfill-corejs2": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.3.tgz", + "integrity": "sha512-bM3gHc337Dta490gg+/AseNB9L4YLHxq1nGKZZSHbhXv4aTYU2MD2cjza1Ru4S6975YLTaL1K8uJf6ukJhhmtw==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.17.7", + "@babel/helper-define-polyfill-provider": "^0.4.0", + "semver": "^6.1.1" + } + }, + "babel-plugin-polyfill-corejs3": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.1.tgz", + "integrity": "sha512-ikFrZITKg1xH6pLND8zT14UPgjKHiGLqex7rGEZCH2EvhsneJaJPemmpQaIZV5AL03II+lXylw3UmddDK8RU5Q==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.4.0", + "core-js-compat": "^3.30.1" + } + }, + "babel-plugin-polyfill-regenerator": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.0.tgz", + "integrity": "sha512-hDJtKjMLVa7Z+LwnTCxoDLQj6wdc+B8dun7ayF2fYieI6OzfuvcLMB32ihJZ4UhCBwNYGl5bg/x/P9cMdnkc2g==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.4.0" + } + }, + "babel-plugin-syntax-object-rest-spread": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", + "integrity": "sha512-C4Aq+GaAj83pRQ0EFgTvw5YO6T3Qz2KGrNRwIj9mSoNHVvdZY4KO2uA6HNtNXCw993iSZnckY1aLW8nOi8i4+w==", + "dev": true + }, + "babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "requires": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + } + }, + "babel-preset-jest": { + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-23.2.0.tgz", + "integrity": "sha512-AdfWwc0PYvDtwr009yyVNh72Ev68os7SsPmOFVX7zSA+STXuk5CV2iMVazZU01bEoHCSwTkgv4E4HOOcODPkPg==", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "^23.2.0", + "babel-plugin-syntax-object-rest-spread": "^6.13.0" + } + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + } + } + }, + "babel-template": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "integrity": "sha512-PCOcLFW7/eazGUKIoqH97sO9A2UYMahsn/yRQ7uOk37iutwjq7ODtcTNF+iFDSHNfkctqsLRjLP7URnOx0T1fg==", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" + } + }, + "babel-traverse": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha512-iSxeXx7apsjCHe9c7n8VtRXGzI2Bk1rBSOJgCCjfyXb6v1aCqE1KSEpq/8SXuVN8Ka/Rh1WDTF0MDzkvTA4MIA==", + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha512-zhe3V/26rCWsEZK8kZN+HaQj5yQ1CilTObixFzKW1UWjqG7618Twz6YEsCnjfg5gBcJh02DrpCkS9h98ZqDY+g==", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha512-xU7bpz2ytJl1bH9cgIurjpg/n8Gohy9GTw81heDYLJQ4RU60dlyJsa+atVF2pI0yMMvKxI9HkKwjePCj5XI1hw==", + "dev": true, + "requires": { + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" + } + }, + "browserslist": { + "version": "4.21.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", + "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001449", + "electron-to-chromium": "^1.4.284", + "node-releases": "^2.0.8", + "update-browserslist-db": "^1.0.10" + } + }, + "bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "requires": { + "node-int64": "^0.4.0" + } + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "dev": true, + "requires": { + "semver": "^7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001482", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001482.tgz", + "integrity": "sha512-F1ZInsg53cegyjroxLNW9DmrEQ1SuGRTO1QlpA0o2/6OpQ0gFeDRoq1yFmnr8Sakn9qwwt9DmbxHB6w167OSuQ==", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true + }, + "chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true + }, + "ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "dev": true + }, + "cjs-module-lexer": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", + "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", + "dev": true + }, + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "dependencies": { + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + } + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true + }, + "collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "dev": true + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "dev": true + }, + "core-js-compat": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.31.0.tgz", + "integrity": "sha512-hM7YCu1cU6Opx7MXNu0NuumM0ezNeAeRKadixyiQELWY3vT3De9S4J5ZBMraWV2vZnrE1Cirl0GtFtDtMUXzPw==", + "dev": true, + "requires": { + "browserslist": "^4.21.5" + } + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true + }, + "define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dev": true, + "requires": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, + "detect-indent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", + "integrity": "sha512-BDKtmHlOzwI7iRuEkhzsnPoi5ypEhWAJB5RvHWe1kMr06js3uK5B3734i3ui5Yd+wOJV1cpE4JnivPD283GU/A==", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true + }, + "diff-sequences": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", + "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "dev": true + }, + "docker-names": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/docker-names/-/docker-names-1.2.1.tgz", + "integrity": "sha512-uh42tvWBp10fnMyJ9z0YL9kql+iolxEnQ+pGZANj+gcg/N2NxjrdHbMXT2Y2Y07A8Jf7KJp/6LkElPCfukCdWg==" + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "electron-to-chromium": { + "version": "1.4.379", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.379.tgz", + "integrity": "sha512-eRMq6Cf4PhjB14R9U6QcXM/VRQ54Gc3OL9LKnFugUIh2AXm3KJlOizlSfVIgjH76bII4zHGK4t0PVTE5qq8dZg==", + "dev": true + }, + "emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "enhanced-resolve": { + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.13.0.tgz", + "integrity": "sha512-eyV8f0y1+bzyfh8xAwW/WTSZpLbjhqc4ne9eGSH4Zo2ejdyiNG9pU6mf9DG8a7+Auk6MFTlNOT4Y2y/9k8GKVg==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + } + }, + "envinfo": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", + "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", + "dev": true + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.21.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", + "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", + "dev": true, + "requires": { + "array-buffer-byte-length": "^1.0.0", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.2.0", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.7", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + } + }, + "es-module-lexer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.2.1.tgz", + "integrity": "sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==", + "dev": true + }, + "es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + } + }, + "es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "eslint": { + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.39.0.tgz", + "integrity": "sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.0.2", + "@eslint/js": "8.39.0", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.0", + "eslint-visitor-keys": "^3.4.0", + "espree": "^9.5.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "eslint-config-standard": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.0.0.tgz", + "integrity": "sha512-/2ks1GKyqSOkH7JFvXJicu0iMpoojkwB+f5Du/1SC0PtBL+s8v30k9njRZ21pm2drKYm2342jFnGWzttxPmZVg==", + "dev": true, + "requires": {} + }, + "eslint-import-resolver-node": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", + "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", + "dev": true, + "requires": { + "debug": "^3.2.7", + "is-core-module": "^2.11.0", + "resolve": "^1.22.1" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } + } + }, + "eslint-module-utils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", + "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", + "dev": true, + "requires": { + "debug": "^3.2.7" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } + } + }, + "eslint-plugin-es": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz", + "integrity": "sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==", + "dev": true, + "requires": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + }, + "dependencies": { + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, + "eslint-plugin-import": { + "version": "2.27.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", + "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", + "dev": true, + "requires": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "array.prototype.flatmap": "^1.3.1", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.7", + "eslint-module-utils": "^2.7.4", + "has": "^1.0.3", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.6", + "resolve": "^1.22.1", + "semver": "^6.3.0", + "tsconfig-paths": "^3.14.1" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } + } + }, + "eslint-plugin-n": { + "version": "15.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.7.0.tgz", + "integrity": "sha512-jDex9s7D/Qial8AGVIHq4W7NswpUD5DPDL2RH8Lzd9EloWUuvUkHfv4FRLMipH5q2UtyurorBkPeNi1wVWNh3Q==", + "dev": true, + "requires": { + "builtins": "^5.0.1", + "eslint-plugin-es": "^4.1.0", + "eslint-utils": "^3.0.0", + "ignore": "^5.1.1", + "is-core-module": "^2.11.0", + "minimatch": "^3.1.2", + "resolve": "^1.22.1", + "semver": "^7.3.8" + }, + "dependencies": { + "semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "eslint-plugin-promise": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", + "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==", + "dev": true, + "requires": {} + }, + "eslint-scope": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", + "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", + "dev": true + }, + "espree": { + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz", + "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==", + "dev": true, + "requires": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true + }, + "expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha512-hxx03P2dJxss6ceIeri9cmYOT4SRs3Zk3afZwWpOsRqLqprhTR8u++SlC+sFGsQr7WGFPdMF7Gjc1njDLDK6UA==", + "dev": true, + "requires": { + "is-posix-bracket": "^0.1.0" + } + }, + "expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha512-AFASGfIlnIbkKPQwX1yHaDjFvh/1gyKJODme52V6IORh69uEYgZp0o9C+qsIGNVEiuuhQU0CSSl++Rlegg1qvA==", + "dev": true, + "requires": { + "fill-range": "^2.1.0" + } + }, + "expect": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.5.0.tgz", + "integrity": "sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg==", + "dev": true, + "requires": { + "@jest/expect-utils": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0" + } + }, + "extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha512-1FOj1LOwn42TMrruOHGt18HemVnbwAmAak7krWk+wa93KXxGbK+2jpezm+ytJYDaBX0/SPLZFHKM7m+tKobWGg==", + "dev": true, + "requires": { + "is-extglob": "^1.0.0" + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true + }, + "fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "requires": { + "bser": "2.1.1" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha512-BTCqyBaWBTsauvnHiE8i562+EdJj+oUpkqWp2R1iCoR8f6oo8STRu3of7WJJ0TqWtxN50a5YFpzYK4Jj9esYfQ==", + "dev": true + }, + "fill-range": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", + "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", + "dev": true, + "requires": { + "is-number": "^2.1.0", + "isobject": "^2.0.0", + "randomatic": "^3.0.0", + "repeat-element": "^1.1.2", + "repeat-string": "^1.5.2" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "requires": { + "is-callable": "^1.1.3" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", + "dev": true + }, + "for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha512-SKmowqGTJoPzLO1T0BBJpkfp3EMacCMOuH40hOUbrbzElVktk4DioXVM99QkLCyKoiuOmyjgcWMpVz2xjE7LZw==", + "dev": true, + "requires": { + "for-in": "^1.0.1" + } + }, + "fs-readdir-recursive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", + "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + } + }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-intrinsic": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha512-ab1S1g1EbO7YzauaJLkgLp7DZVAqj9M/dvKlTt8DkXA2tiOIcSMrlVI2J1RZyB5iJVccEscjGn+kpOG9788MHA==", + "dev": true, + "requires": { + "glob-parent": "^2.0.0", + "is-glob": "^2.0.0" + } + }, + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha512-JDYOvfxio/t42HKdxkAYaCiBN7oYiuxykOxKxdaUW5Qn0zaYN3gRQWolrwdnf0shM9/EP0ebuuTmyoXNr1cC5w==", + "dev": true, + "requires": { + "is-glob": "^2.0.0" + } + }, + "glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3" + } + }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.3" + } + }, + "graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.1" + } + }, + "has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true + }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, + "interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, + "is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "requires": { + "has-bigints": "^1.0.1" + } + }, + "is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true + }, + "is-core-module": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz", + "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-dotfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha512-9YclgOGtN/f8zx0Pr4FQYMdibBiTaH3sn52vjYip4ZSf6C4/6RfTEZ+MR4GvKhCxdPh21Bg42/WL55f6KSnKpg==", + "dev": true + }, + "is-equal-shallow": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha512-0EygVC5qPvIyb+gSz7zdD5/AAoS6Qrx1e//6N4yv4oNm30kqvdmG66oZFWVlQHUWe5OjP08FuTw2IdT0EOTcYA==", + "dev": true, + "requires": { + "is-primitive": "^2.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha512-7Q+VbVafe6x2T+Tu6NcOf6sRklazEPmBoB3IWk3WdGZM2iGUwU/Oe3Wtq5lSEkDTTlpp8yx+5t4pzO/i9Ty1ww==", + "dev": true + }, + "is-finite": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", + "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha512-a1dBeB19NXsf/E0+FHqkagizel/LQw2DjSQpvQrj3zT+jYPpaUCryPnrQajXKFLCMuf4I6FhRpaGtw4lPrG6Eg==", + "dev": true, + "requires": { + "is-extglob": "^1.0.0" + } + }, + "is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true + }, + "is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha512-QUzH43Gfb9+5yckcrSA0VBDwEtDUchrk4F6tfJZQuNzDJbEDB9cZNzSfXGQ1jqmdDY/kl41lUOWM9syA8z8jlg==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true + } + } + }, + "is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha512-Yu68oeXJ7LeWNmZ3Zov/xg/oDBnBK2RNxwYY1ilNJX+tKKZqgPK+qOn/Gs9jEu66KDY9Netf5XLKNGzas/vPfQ==", + "dev": true + }, + "is-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha512-N3w1tFaRfk3UrPfqeRyD+GYDASU3W5VinKhlORy8EWVf/sIdDL9GAcew85XmktCfH+ngG7SRXEVDoO18WMdB/Q==", + "dev": true + }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + } + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==", + "dev": true + }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + }, + "istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "requires": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + } + }, + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.5.0.tgz", + "integrity": "sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==", + "dev": true, + "requires": { + "@jest/core": "^29.5.0", + "@jest/types": "^29.5.0", + "import-local": "^3.0.2", + "jest-cli": "^29.5.0" + } + }, + "jest-changed-files": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.5.0.tgz", + "integrity": "sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==", + "dev": true, + "requires": { + "execa": "^5.0.0", + "p-limit": "^3.1.0" + } + }, + "jest-circus": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.5.0.tgz", + "integrity": "sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA==", + "dev": true, + "requires": { + "@jest/environment": "^29.5.0", + "@jest/expect": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.5.0", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.5.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-cli": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.5.0.tgz", + "integrity": "sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==", + "dev": true, + "requires": { + "@jest/core": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "prompts": "^2.0.1", + "yargs": "^17.3.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-config": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.5.0.tgz", + "integrity": "sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.5.0", + "@jest/types": "^29.5.0", + "babel-jest": "^29.5.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.5.0", + "jest-environment-node": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "babel-jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.5.0.tgz", + "integrity": "sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q==", + "dev": true, + "requires": { + "@jest/transform": "^29.5.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.5.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + } + }, + "babel-plugin-jest-hoist": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz", + "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==", + "dev": true, + "requires": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-preset-jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz", + "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "^29.5.0", + "babel-preset-current-node-syntax": "^1.0.0" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, + "jest-diff": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", + "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^29.4.3", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-docblock": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz", + "integrity": "sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==", + "dev": true, + "requires": { + "detect-newline": "^3.0.0" + } + }, + "jest-each": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.5.0.tgz", + "integrity": "sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA==", + "dev": true, + "requires": { + "@jest/types": "^29.5.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "jest-util": "^29.5.0", + "pretty-format": "^29.5.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-environment-node": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.5.0.tgz", + "integrity": "sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw==", + "dev": true, + "requires": { + "@jest/environment": "^29.5.0", + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" + } + }, + "jest-get-type": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", + "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", + "dev": true + }, + "jest-haste-map": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.5.0.tgz", + "integrity": "sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA==", + "dev": true, + "requires": { + "@jest/types": "^29.5.0", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.5.0", + "jest-worker": "^29.5.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "dependencies": { + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "jest-worker": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz", + "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==", + "dev": true, + "requires": { + "@types/node": "*", + "jest-util": "^29.5.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + } + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, + "jest-leak-detector": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz", + "integrity": "sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow==", + "dev": true, + "requires": { + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + } + }, + "jest-matcher-utils": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz", + "integrity": "sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-message-util": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.5.0.tgz", + "integrity": "sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.5.0", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, + "jest-mock": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.5.0.tgz", + "integrity": "sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw==", + "dev": true, + "requires": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-util": "^29.5.0" + } + }, + "jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "requires": {} + }, + "jest-regex-util": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz", + "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", + "dev": true + }, + "jest-resolve": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.5.0.tgz", + "integrity": "sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-resolve-dependencies": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz", + "integrity": "sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg==", + "dev": true, + "requires": { + "jest-regex-util": "^29.4.3", + "jest-snapshot": "^29.5.0" + } + }, + "jest-runner": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.5.0.tgz", + "integrity": "sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ==", + "dev": true, + "requires": { + "@jest/console": "^29.5.0", + "@jest/environment": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.4.3", + "jest-environment-node": "^29.5.0", + "jest-haste-map": "^29.5.0", + "jest-leak-detector": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-resolve": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-util": "^29.5.0", + "jest-watcher": "^29.5.0", + "jest-worker": "^29.5.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "jest-worker": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz", + "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==", + "dev": true, + "requires": { + "@types/node": "*", + "jest-util": "^29.5.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-runtime": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.5.0.tgz", + "integrity": "sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw==", + "dev": true, + "requires": { + "@jest/environment": "^29.5.0", + "@jest/fake-timers": "^29.5.0", + "@jest/globals": "^29.5.0", + "@jest/source-map": "^29.4.3", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-mock": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-snapshot": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.5.0.tgz", + "integrity": "sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/babel__traverse": "^7.0.6", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.5.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.5.0", + "semver": "^7.3.5" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-util": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.5.0.tgz", + "integrity": "sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==", + "dev": true, + "requires": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-validate": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.5.0.tgz", + "integrity": "sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ==", + "dev": true, + "requires": { + "@jest/types": "^29.5.0", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "leven": "^3.1.0", + "pretty-format": "^29.5.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-watcher": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.5.0.tgz", + "integrity": "sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA==", + "dev": true, + "requires": { + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.5.0", + "string-length": "^4.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "js-sdsl": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", + "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==", + "dev": true + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha512-RjTcuD4xjtthQkaWH7dFlH85L+QaVtSoOyGdZ3g6HFhS9dFNDfLyqgm2NFe2X6cQpeFmt0452FJjFG5UameExg==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "jsesc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "integrity": "sha512-Mke0DA0QjUWuJlhsE0ZPPhYiJkRap642SmI/4ztCFaUs6V2AiH1sfecc+57NgaryfAA2VR3v6O+CSjC1jZJKOA==", + "dev": true + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true + }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + }, + "dependencies": { + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + } + } + }, + "loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "requires": { + "tmpl": "1.0.5" + } + }, + "math-random": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", + "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==", + "dev": true + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha512-LnU2XFEk9xxSJ6rfgAry/ty5qwUTyHYOBU0g4R6tIw5ljwgGIBmiKhRWLw5NpMOnrgUNcDJ4WMp8rl3sYVHLNA==", + "dev": true, + "requires": { + "arr-diff": "^2.0.0", + "array-unique": "^0.2.1", + "braces": "^1.8.2", + "expand-brackets": "^0.1.4", + "extglob": "^0.3.1", + "filename-regex": "^2.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.1", + "kind-of": "^3.0.2", + "normalize-path": "^2.0.1", + "object.omit": "^2.0.0", + "parse-glob": "^3.0.4", + "regex-cache": "^0.4.2" + } + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "requires": { + "mime-db": "1.52.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node-releases": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", + "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", + "dev": true + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true + }, + "object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + } + }, + "object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha512-UiAM5mhmIuKLsOvrL+B0U2d1hXHF3bFYWIuH1LMpuV2EJEHG1Ntz06PgLEHjm6VFd87NpH8rastvPoyv6UW2fA==", + "dev": true, + "requires": { + "for-own": "^0.1.4", + "is-extendable": "^0.1.1" + } + }, + "object.values": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha512-FC5TeK0AwXzq3tUBFtH74naWkPQCEWs4K+xMxWZBlKDWu0bVHXGZa+KKqxKidd7xwhdZ19ZNuF2uO1M/r196HA==", + "dev": true, + "requires": { + "glob-base": "^0.3.0", + "is-dotfile": "^1.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.0" + } + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true + } + } + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + } + } + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha512-s/46sYeylUfHNjI+sA/78FAHlmIuKqI9wNnzEOGehAlUUYeObv5C2mOinXBjyUyWmJ2SfcS2/ydApH4hTF4WXQ==", + "dev": true + }, + "prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true + }, + "pretty-format": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", + "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", + "dev": true, + "requires": { + "@jest/schemas": "^29.4.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "requires": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + } + }, + "punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true + }, + "pure-rand": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.2.tgz", + "integrity": "sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ==", + "dev": true + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "randomatic": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", + "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", + "dev": true, + "requires": { + "is-number": "^4.0.0", + "kind-of": "^6.0.0", + "math-random": "^1.0.1" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + } + } + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ==", + "dev": true, + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A==", + "dev": true, + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + } + } + }, + "rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "requires": { + "resolve": "^1.20.0" + } + }, + "regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "regenerate-unicode-properties": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", + "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", + "dev": true, + "requires": { + "regenerate": "^1.4.2" + } + }, + "regex-cache": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", + "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "dev": true, + "requires": { + "is-equal-shallow": "^0.1.3" + } + }, + "regexp.prototype.flags": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", + "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "functions-have-names": "^1.2.3" + } + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==", + "dev": true + }, + "repeat-element": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", + "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "dev": true + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha512-ZqtSMuVybkISo2OWvqvm7iHSWngvdaW3IpsT9/uP8v4gMi591LY6h35wdOfvQdWCKFWZWm2Y1Opp4kV7vQKT6A==", + "dev": true, + "requires": { + "is-finite": "^1.0.0" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug==", + "dev": true + }, + "resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dev": true, + "requires": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + } + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + } + }, + "schema-utils": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.2.tgz", + "integrity": "sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "requires": { + "kind-of": "^6.0.2" + }, + "dependencies": { + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + } + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true + }, + "spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz", + "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + } + } + }, + "string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "requires": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "string.prototype.trim": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", + "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "dev": true + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true + }, + "terser": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.17.1.tgz", + "integrity": "sha512-hVl35zClmpisy6oaoKALOpS0rDYLxRFLHhRuDlEGTKey9qHjS1w9GMORjuwIMt70Wan4lwsLYyWDVnWgF+KUEw==", + "dev": true, + "requires": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + } + } + }, + "terser-webpack-plugin": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.7.tgz", + "integrity": "sha512-AfKwIktyP7Cu50xNjXF/6Qb5lBNzYaWpU6YfoX3uZicTx0zTy0stDDCsvjDapKsSDvOeWo5MEq4TmdBy2cNoHw==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.17", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.16.5" + } + }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha512-lxrWP8ejsq+7E3nNjwYmUBMAgjMTZoTI+sdBOpvNyijeDLa29LUn9QaoXAHv4+Z578hbmHHJKZknzxVtvo77og==", + "dev": true + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha512-WZGXGstmCWgeevgTL54hrCuw1dyMQIzWy7ZfqRJfSmJZBwklI15egmQytFP6bPidmw3M8d5yEowl1niq4vmqZw==", + "dev": true + }, + "tsconfig-paths": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", + "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + } + } + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, + "typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + } + }, + "unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + } + }, + "unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true + }, + "unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "requires": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true + }, + "unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true + }, + "update-browserslist-db": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "dev": true, + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "v8-to-istanbul": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", + "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0" + } + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "requires": { + "makeerror": "1.0.12" + } + }, + "watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "requires": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + } + }, + "webpack": { + "version": "5.81.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.81.0.tgz", + "integrity": "sha512-AAjaJ9S4hYCVODKLQTgG5p5e11hiMawBwV2v8MYLE0C/6UAGLuAF4n1qa9GOwdxnicaP+5k6M5HrLmD4+gIB8Q==", + "dev": true, + "requires": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.0", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.7.6", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.13.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.1.2", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.7", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "dependencies": { + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + } + } + }, + "webpack-cli": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.0.2.tgz", + "integrity": "sha512-4y3W5Dawri5+8dXm3+diW6Mn1Ya+Dei6eEVAdIduAmYNLzv1koKVAqsfgrrc9P2mhrYHQphx5htnGkcNwtubyQ==", + "dev": true, + "requires": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^2.0.1", + "@webpack-cli/info": "^2.0.1", + "@webpack-cli/serve": "^2.0.2", + "colorette": "^2.0.14", + "commander": "^10.0.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^5.7.3" + }, + "dependencies": { + "commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true + } + } + }, + "webpack-merge": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", + "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", + "dev": true, + "requires": { + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" + } + }, + "webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, + "which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + } + }, + "wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/datajunction-clients/javascript/package.json b/datajunction-clients/javascript/package.json new file mode 100644 index 000000000..3baddf56a --- /dev/null +++ b/datajunction-clients/javascript/package.json @@ -0,0 +1,50 @@ +{ + "name": "datajunction", + "version": "0.0.1-rc.1", + "description": "A Javascript client for interacting with a DataJunction server", + "module": "src/index.js", + "scripts": { + "test": "jest", + "test-watch": "jest --watch", + "build": "rm -rf dist/* && webpack && babel src -d dist", + "lint": "prettier \"src/**/*.{js,jsx}\"", + "format": "prettier --write \"src/**/*.{js,jsx}\"" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/DataJunction/dj.git" + }, + "keywords": [ + "datajunction", + "metrics", + "metrics-platform", + "semantic-layer" + ], + "author": "DataJunction Authors", + "license": "MIT", + "bugs": { + "url": "https://github.com/DataJunction/dj/issues" + }, + "homepage": "https://github.com/DataJunction/dj#readme", + "devDependencies": { + "@babel/cli": "^7.0.0", + "@babel/core": "^7.0.0", + "@babel/plugin-proposal-object-rest-spread": "^7.0.0", + "@babel/preset-env": "^7.0.0", + "babel-core": "^7.0.0-bridge.0", + "babel-jest": "^23.4.2", + "eslint": "^8.39.0", + "eslint-config-standard": "^17.0.0", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-n": "^15.7.0", + "eslint-plugin-promise": "^6.1.1", + "jest": "^29.5.0", + "prettier": "^2.8.8", + "webpack": "^5.81.0", + "webpack-cli": "^5.0.2" + }, + "dependencies": { + "@babel/core": "^7.22.5", + "docker-names": "^1.2.1" + } +} diff --git a/datajunction-clients/javascript/src/httpclient.js b/datajunction-clients/javascript/src/httpclient.js new file mode 100644 index 000000000..1e41965d4 --- /dev/null +++ b/datajunction-clients/javascript/src/httpclient.js @@ -0,0 +1,83 @@ +export default class HttpClient { + constructor(options = {}) { + this._baseURL = options.baseURL || '' + this._headers = options.headers || {} + } + + async _fetchJSON(endpoint, options = {}) { + const res = await fetch(this._baseURL + endpoint, { + ...options, + headers: this._headers, + }) + + if (!res.ok) + throw new Error( + res.statusText + ` (${endpoint}, ${JSON.stringify(options)})` + ) + + if (options.parseResponse !== false && res.status !== 204) { + return res.json() + } + + return undefined + } + + setHeader(key, value) { + this._headers[key] = value + return this + } + + getHeader(key) { + return this._headers[key] + } + + setBasicAuth(username, password) { + this._headers.Authorization = `Basic ${btoa(`${username}:${password}`)}` + return this + } + + setBearerAuth(token) { + this._headers.Authorization = `Bearer ${token}` + return this + } + + async get(endpoint, options = {}) { + return this._fetchJSON(endpoint, { + ...options, + method: 'GET', + }) + } + + async post(endpoint, body, options = {}) { + return this._fetchJSON(endpoint, { + ...options, + body: body ? JSON.stringify(body) : undefined, + method: 'POST', + }) + } + + async put(endpoint, body, options = {}) { + return this._fetchJSON(endpoint, { + ...options, + body: body ? JSON.stringify(body) : undefined, + method: 'PUT', + }) + } + + async patch(endpoint, operations, options = {}) { + return this._fetchJSON(endpoint, { + parseResponse: false, + ...options, + body: JSON.stringify(operations), + method: 'PATCH', + }) + } + + async delete(endpoint, options = {}) { + return this._fetchJSON(endpoint, { + parseResponse: false, + ...options, + method: 'DELETE', + }) + } +} diff --git a/datajunction-clients/javascript/src/index.js b/datajunction-clients/javascript/src/index.js new file mode 100644 index 000000000..41290675b --- /dev/null +++ b/datajunction-clients/javascript/src/index.js @@ -0,0 +1,298 @@ +import HttpClient from './httpclient.js' + +export class DJClient extends HttpClient { + constructor( + baseURL, + namespace, + engineName = null, + engineVersion = null, + httpAgent = null + ) { + super( + { + baseURL, + }, + httpAgent + ) + this.namespace = namespace + this.engineName = engineName + this.engineVersion = engineVersion + } + + get healthcheck() { + return { + get: () => this.get('/health/'), + } + } + + get catalogs() { + return { + list: () => this.get('/catalogs/'), + get: (catalog) => this.get(`/catalogs/${catalog}/`), + create: (catalog) => + this.setHeader('Content-Type', 'application/json').post( + `/catalogs/`, + catalog + ), + addEngine: (catalog, engineName, engineVersion) => + this.setHeader('Content-Type', 'application/json').post( + `/catalogs/${catalog}/engines/`, + [ + { + name: engineName, + version: engineVersion, + }, + ] + ), + } + } + + get engines() { + return { + list: () => this.get('/engines/'), + get: (engineName, engineVersion) => + this.get(`/engines/${engineName}/${engineVersion}/`), + create: (engine) => this.post('/engines/', engine), + } + } + + get addEngineToCatalog() { + return { + set: (catalogName, engine) => + this.setHeader('Content-Type', 'application/json').post( + `/catalogs/${catalogName}/engines/`, + [engine] + ), + } + } + + get namespaces() { + return { + list: () => this.get('/namespaces/'), + nodes: (namespace) => this.get(`/namespaces/${namespace}/`), + create: (namespace) => + this.setHeader('Content-Type', 'application/json').post( + `/namespaces/${namespace}/` + ), + } + } + + get commonDimensions() { + return { + list: (metrics) => { + const metricsQuery = + '?' + metrics.map((m) => `metric=${m}`).join('&') + return this.get('/metrics/common/dimensions/' + metricsQuery) + }, + } + } + + get nodes() { + return { + get: (nodeName) => this.get(`/nodes/${nodeName}/`), + validate: (nodeDetails) => + this.setHeader('Content-Type', 'application/json').post( + '/nodes/validate/', + nodeDetails + ), + update: (nodeName, nodeDetails) => + this.setHeader('Content-Type', 'application/json').patch( + `/nodes/${nodeName}/`, + nodeDetails + ), + revisions: (nodeName) => this.get(`/nodes/${nodeName}/revisions/`), + downstream: (nodeName) => + this.get(`/nodes/${nodeName}/downstream/`), + upstream: (nodeName) => this.get(`/nodes/${nodeName}/upstream/`), + publish: (nodeName) => this.patch(`/nodes/${nodeName}/`, {'mode': 'published'}) + } + } + + get sources() { + return { + create: (sourceDetails) => + this.setHeader('Content-Type', 'application/json').post( + '/nodes/source/', + sourceDetails + ), + list: () => this.get(`/namespaces/${this.namespace}/?type_=source`), + } + } + + get transforms() { + return { + create: (transformDetails) => + this.setHeader('Content-Type', 'application/json').post( + '/nodes/transform/', + transformDetails + ), + list: () => + this.get(`/namespaces/${this.namespace}/?type_=transform`), + } + } + + get dimensions() { + return { + create: (dimensionDetails) => + this.setHeader('Content-Type', 'application/json').post( + '/nodes/dimension/', + dimensionDetails + ), + list: () => + this.get(`/namespaces/${this.namespace}/?type_=dimension`), + link: (nodeName, nodeColumn, dimension, dimensionColumn) => + this.post( + `/nodes/${nodeName}/columns/${nodeColumn}/?dimension=${dimension}&dimension_column=${dimensionColumn}` + ), + } + } + + get metrics() { + return { + get: (metricName) => this.get(`/metrics/${metricName}/`), + create: (metricDetails) => + this.setHeader('Content-Type', 'application/json').post( + '/nodes/metric/', + metricDetails + ), + list: () => this.get(`/namespaces/${this.namespace}/?type_=metric`), + all: () => this.get(`/metrics/`), + } + } + + get cubes() { + return { + get: (cubeName) => this.get(`/cubes/${cubeName}/`), + create: (cubeDetails) => + this.setHeader('Content-Type', 'application/json').post( + '/nodes/cube/', + cubeDetails + ), + } + } + + get tags() { + return { + list: () => this.get('/tags/'), + get: (tagName) => this.get(`/tags/${tagName}/`), + create: (tagData) => + this.setHeader('Content-Type', 'application/json').post( + '/tags/', + tagData + ), + update: (tagName, tagData) => + this.setHeader('Content-Type', 'application/json').patch( + `/tags/${tagName}/`, + tagData + ), + set: (nodeName, tagName) => + this.post(`/nodes/${nodeName}/tag/?tag_name=${tagName}`), + listNodes: (tagName) => this.get(`/tags/${tagName}/nodes/`), + } + } + + get attributes() { + return { + list: () => this.get('/attributes/'), + create: (attributeData) => + this.setHeader('Content-Type', 'application/json').post( + '/attributes/', + attributeData + ), + } + } + + get materializationConfigs() { + return { + update: (nodeName, materializationDetails) => + this.setHeader('Content-Type', 'application/json').post( + `/nodes/${nodeName}/materialization/`, + materializationDetails + ), + } + } + + get columnAttributes() { + return { + set: (nodeName, columnAttribute) => + this.setHeader('Content-Type', 'application/json').post( + `/nodes/${nodeName}/attributes/`, + [columnAttribute] + ), + } + } + + get availabilityState() { + return { + set: (nodeName, availabilityState) => + this.setHeader('Content-Type', 'application/json').post( + `/data/${nodeName}/availability/`, + availabilityState + ), + } + } + + get sql() { + return { + get: ( + metrics, + dimensions, + filters, + engineName = null, + engineVersion = null + ) => { + const metricsQuery = + '?' + metrics.map((m) => `metrics=${m}`).join('&') + const dimensionsQuery = dimensions + .map((d) => `dimensions=${d}`) + .join('&') + const filtersQuery = filters + .map((f) => `filters=${f}`) + .join('&') + const engineNameP = engineName ? `&engine=${engineName}` : '' + const engineVersionP = engineVersion + ? `&engine_version=${engineVersion}` + : '' + return this.get( + `/sql/${metricsQuery}&${dimensionsQuery}${filtersQuery}${engineNameP}${engineVersionP}` + ) + }, + } + } + + get data() { + return { + get: ( + metrics, + dimensions, + filters, + async_ = false, + engineName = null, + engineVersion = null + ) => { + const metricsQuery = + '?' + metrics.map((m) => `metrics=${m}`).join('&') + const dimensionsQuery = dimensions + .map((d) => `dimensions=${d}`) + .join('&') + const filtersQuery = filters + .map((f) => `filters=${f}`) + .join('&') + const asyncP = async_ ? `&async_=${async_}` : '' + const engineNameP = engineName ? `&engine=${engineName}` : '' + const engineVersionP = engineVersion + ? `&engine_version=${engineVersion}` + : '' + const data = this.get( + `/data/${metricsQuery}&${dimensionsQuery}${filtersQuery}${asyncP}${engineNameP}${engineVersionP}` + ).then((data) => { + return { + columns: data.results[0].columns, + data: data.results[0].rows, + } + }) + return data + }, + } + } +} diff --git a/datajunction-clients/javascript/src/index.test.js b/datajunction-clients/javascript/src/index.test.js new file mode 100644 index 000000000..cc3fee120 --- /dev/null +++ b/datajunction-clients/javascript/src/index.test.js @@ -0,0 +1,592 @@ +const { DJClient } = require('./index') +var dockerNames = require('docker-names') + +test('should return something', async () => { + const randomName = dockerNames.getRandomName(true) + const namespace = `integration.javascript.${randomName}` + const dj = new DJClient('http://localhost:8000') + + // Create a namespace + const namespace_created = await dj.namespaces.create(namespace) + expect(namespace_created).toEqual({ + message: `Node namespace \`${namespace}\` has been successfully created`, + }) + + // List namespaces + const existing_namespaces = await dj.namespaces.list() + expect(existing_namespaces).toContainEqual({ namespace: namespace }) + + // Create a source + await dj.sources.create({ + name: `${namespace}.repair_orders`, + description: 'Repair orders', + catalog: 'warehouse', + schema_: 'roads', + table: 'repair_orders', + mode: 'published', + columns: [ + { name: 'repair_order_id', type: 'int' }, + { name: 'municipality_id', type: 'string' }, + { name: 'hard_hat_id', type: 'int' }, + { name: 'order_date', type: 'timestamp' }, + { name: 'required_date', type: 'timestamp' }, + { name: 'dispatched_date', type: 'timestamp' }, + { name: 'dispatcher_id', type: 'int' }, + ], + }) + + // Get source + const source1 = await dj.nodes.get(`${namespace}.repair_orders`) + expect(source1.name).toBe(`${namespace}.repair_orders`) + expect(source1.status).toBe('valid') + expect(source1.mode).toBe('published') + + // Create a transform + await dj.transforms.create({ + name: `${namespace}.repair_orders_w_dispatchers`, + description: 'Repair orders that have a dispatcher', + mode: 'published', + query: `SELECT + repair_order_id, + municipality_id, + hard_hat_id, + dispatcher_id + FROM ${namespace}.repair_orders + WHERE dispatcher_id IS NOT NULL`, + }) + + // Get transform + const transform1 = await dj.nodes.get( + `${namespace}.repair_orders_w_dispatchers` + ) + expect(transform1.name).toBe(`${namespace}.repair_orders_w_dispatchers`) + expect(transform1.status).toBe('valid') + expect(transform1.mode).toBe('published') + + // Create a source and dimension node + const source2 = await dj.sources.create({ + name: `${namespace}.dispatchers`, + description: + 'Different third party dispatcher companies that coordinate repairs', + catalog: 'warehouse', + schema_: 'roads', + table: 'dispatchers', + mode: 'published', + columns: [ + { name: 'dispatcher_id', type: 'int' }, + { name: 'company_name', type: 'string' }, + { name: 'phone', type: 'string' }, + ], + }) + expect(source2.name).toBe(`${namespace}.dispatchers`) + expect(source2.status).toBe('valid') + expect(source2.mode).toBe('published') + await dj.dimensions.create({ + name: `${namespace}.all_dispatchers`, + description: 'All dispatchers', + primary_key: ['dispatcher_id'], + mode: 'published', + query: `SELECT + dispatcher_id, + company_name, + phone + FROM ${namespace}.dispatchers`, + }) + const dimension1 = await dj.nodes.get(`${namespace}.all_dispatchers`) + expect(dimension1.name).toBe(`${namespace}.all_dispatchers`) + expect(dimension1.status).toBe('valid') + expect(dimension1.mode).toBe('published') + + // Create metrics + await dj.metrics.create({ + name: `${namespace}.num_repair_orders`, + description: 'Number of repair orders', + mode: 'published', + query: `SELECT + count(repair_order_id) + FROM ${namespace}.repair_orders`, + }) + const metric1 = await dj.nodes.get(`${namespace}.num_repair_orders`) + expect(metric1.name).toBe(`${namespace}.num_repair_orders`) + expect(metric1.status).toBe('valid') + expect(metric1.mode).toBe('published') + + // List metrics + const list_of_metrics = await dj.metrics.all() + expect(list_of_metrics).toContain(`${namespace}.num_repair_orders`) + + // Create a dimension link + const dimension_linked_message = await dj.dimensions.link( + `${namespace}.repair_orders`, + 'dispatcher_id', + `${namespace}.all_dispatchers`, + 'dispatcher_id' + ) + expect(dimension_linked_message).toEqual({ + message: `Dimension node ${namespace}.all_dispatchers has been successfully linked to column dispatcher_id on node ${namespace}.repair_orders`, + }) + + const common_dimensions = await dj.commonDimensions.list([ + 'default.num_repair_orders', + 'default.avg_repair_price', + 'default.total_repair_cost', + ]) + + expect(common_dimensions).toEqual([ + { + name: 'default.dispatcher.company_name', + type: 'string', + path: [ + 'default.repair_orders.repair_order_id', + 'default.repair_order.dispatcher_id', + ], + }, + { + name: 'default.dispatcher.dispatcher_id', + type: 'int', + path: [ + 'default.repair_orders.repair_order_id', + 'default.repair_order.dispatcher_id', + ], + }, + { + name: 'default.dispatcher.phone', + type: 'string', + path: [ + 'default.repair_orders.repair_order_id', + 'default.repair_order.dispatcher_id', + ], + }, + { + name: 'default.hard_hat.address', + type: 'string', + path: [ + 'default.repair_orders.repair_order_id', + 'default.repair_order.hard_hat_id', + ], + }, + { + name: 'default.hard_hat.birth_date', + type: 'date', + path: [ + 'default.repair_orders.repair_order_id', + 'default.repair_order.hard_hat_id', + ], + }, + { + name: 'default.hard_hat.city', + type: 'string', + path: [ + 'default.repair_orders.repair_order_id', + 'default.repair_order.hard_hat_id', + ], + }, + { + name: 'default.hard_hat.contractor_id', + type: 'int', + path: [ + 'default.repair_orders.repair_order_id', + 'default.repair_order.hard_hat_id', + ], + }, + { + name: 'default.hard_hat.country', + type: 'string', + path: [ + 'default.repair_orders.repair_order_id', + 'default.repair_order.hard_hat_id', + ], + }, + { + name: 'default.hard_hat.first_name', + type: 'string', + path: [ + 'default.repair_orders.repair_order_id', + 'default.repair_order.hard_hat_id', + ], + }, + { + name: 'default.hard_hat.hard_hat_id', + type: 'int', + path: [ + 'default.repair_orders.repair_order_id', + 'default.repair_order.hard_hat_id', + ], + }, + { + name: 'default.hard_hat.hire_date', + type: 'date', + path: [ + 'default.repair_orders.repair_order_id', + 'default.repair_order.hard_hat_id', + ], + }, + { + name: 'default.hard_hat.last_name', + type: 'string', + path: [ + 'default.repair_orders.repair_order_id', + 'default.repair_order.hard_hat_id', + ], + }, + { + name: 'default.hard_hat.manager', + type: 'int', + path: [ + 'default.repair_orders.repair_order_id', + 'default.repair_order.hard_hat_id', + ], + }, + { + name: 'default.hard_hat.postal_code', + type: 'string', + path: [ + 'default.repair_orders.repair_order_id', + 'default.repair_order.hard_hat_id', + ], + }, + { + name: 'default.hard_hat.state', + type: 'string', + path: [ + 'default.repair_orders.repair_order_id', + 'default.repair_order.hard_hat_id', + ], + }, + { + name: 'default.hard_hat.title', + type: 'string', + path: [ + 'default.repair_orders.repair_order_id', + 'default.repair_order.hard_hat_id', + ], + }, + { + name: 'default.municipality_dim.contact_name', + type: 'string', + path: [ + 'default.repair_orders.repair_order_id', + 'default.repair_order.municipality_id', + ], + }, + { + name: 'default.municipality_dim.contact_title', + type: 'string', + path: [ + 'default.repair_orders.repair_order_id', + 'default.repair_order.municipality_id', + ], + }, + { + name: 'default.municipality_dim.local_region', + type: 'string', + path: [ + 'default.repair_orders.repair_order_id', + 'default.repair_order.municipality_id', + ], + }, + { + name: 'default.municipality_dim.municipality_id', + type: 'string', + path: [ + 'default.repair_orders.repair_order_id', + 'default.repair_order.municipality_id', + ], + }, + { + name: 'default.municipality_dim.municipality_type_desc', + type: 'string', + path: [ + 'default.repair_orders.repair_order_id', + 'default.repair_order.municipality_id', + ], + }, + { + name: 'default.municipality_dim.municipality_type_id', + type: 'string', + path: [ + 'default.repair_orders.repair_order_id', + 'default.repair_order.municipality_id', + ], + }, + { + name: 'default.municipality_dim.state_id', + type: 'int', + path: [ + 'default.repair_orders.repair_order_id', + 'default.repair_order.municipality_id', + ], + }, + { + name: 'default.repair_orders.repair_order_id', + type: 'int', + path: [], + }, + { + name: 'default.us_state.state_abbr', + type: 'string', + path: [ + 'default.repair_orders.repair_order_id', + 'default.repair_order.hard_hat_id', + 'default.hard_hat.state', + ], + }, + { + name: 'default.us_state.state_id', + type: 'int', + path: [ + 'default.repair_orders.repair_order_id', + 'default.repair_order.hard_hat_id', + 'default.hard_hat.state', + ], + }, + { + name: 'default.us_state.state_name', + type: 'string', + path: [ + 'default.repair_orders.repair_order_id', + 'default.repair_order.hard_hat_id', + 'default.hard_hat.state', + ], + }, + { + name: 'default.us_state.state_region', + type: 'int', + path: [ + 'default.repair_orders.repair_order_id', + 'default.repair_order.hard_hat_id', + 'default.hard_hat.state', + ], + }, + { + name: 'default.us_state.state_region_description', + type: 'string', + path: [ + 'default.repair_orders.repair_order_id', + 'default.repair_order.hard_hat_id', + 'default.hard_hat.state', + ], + }, + ]) + + const query = await dj.sql.get( + [ + 'default.num_repair_orders', + 'default.avg_repair_price', + 'default.total_repair_cost', + ], + [ + 'default.us_state.state_abbr', + 'default.us_state.state_id', + 'default.us_state.state_name', + ], + [] + ) + const trimmedQuery = query.sql.replace(/\s+/g, '') + expect(trimmedQuery).toBe( + `WITH + m0_default_DOT_num_repair_orders AS (SELECT default_DOT_us_state.state_abbr, + default_DOT_us_state.state_id, + default_DOT_us_state.state_name, + count(default_DOT_repair_orders.repair_order_id) default_DOT_num_repair_orders + FROM roads.repair_orders AS default_DOT_repair_orders LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders) + AS default_DOT_repair_order ON default_DOT_repair_orders.repair_order_id = default_DOT_repair_order.repair_order_id + LEFT OUTER JOIN (SELECT default_DOT_hard_hats.birth_date, + default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.hire_date, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats) + AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + LEFT OUTER JOIN (SELECT default_DOT_us_states.state_abbr, + default_DOT_us_states.state_id, + default_DOT_us_states.state_name + FROM roads.us_states AS default_DOT_us_states LEFT JOIN roads.us_region AS default_DOT_us_region ON default_DOT_us_states.state_region = default_DOT_us_region.us_region_description) + AS default_DOT_us_state ON default_DOT_hard_hat.state = default_DOT_us_state.state_abbr + GROUP BY default_DOT_us_state.state_abbr, default_DOT_us_state.state_id, default_DOT_us_state.state_name + ), + m1_default_DOT_avg_repair_price AS (SELECT default_DOT_us_state.state_abbr, + default_DOT_us_state.state_id, + default_DOT_us_state.state_name, + avg(default_DOT_repair_order_details.price) default_DOT_avg_repair_price + FROM roads.repair_order_details AS default_DOT_repair_order_details LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders) + AS default_DOT_repair_order ON default_DOT_repair_order_details.repair_order_id = default_DOT_repair_order.repair_order_id + LEFT OUTER JOIN (SELECT default_DOT_hard_hats.birth_date, + default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.hire_date, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats) + AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + LEFT OUTER JOIN (SELECT default_DOT_us_states.state_abbr, + default_DOT_us_states.state_id, + default_DOT_us_states.state_name + FROM roads.us_states AS default_DOT_us_states LEFT JOIN roads.us_region AS default_DOT_us_region ON default_DOT_us_states.state_region = default_DOT_us_region.us_region_description) + AS default_DOT_us_state ON default_DOT_hard_hat.state = default_DOT_us_state.state_abbr + GROUP BY default_DOT_us_state.state_abbr, default_DOT_us_state.state_id, default_DOT_us_state.state_name + ), + m2_default_DOT_total_repair_cost AS (SELECT default_DOT_us_state.state_abbr, + default_DOT_us_state.state_id, + default_DOT_us_state.state_name, + sum(default_DOT_repair_order_details.price) default_DOT_total_repair_cost + FROM roads.repair_order_details AS default_DOT_repair_order_details LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders) + AS default_DOT_repair_order ON default_DOT_repair_order_details.repair_order_id = default_DOT_repair_order.repair_order_id + LEFT OUTER JOIN (SELECT default_DOT_hard_hats.birth_date, + default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.hire_date, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats) + AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + LEFT OUTER JOIN (SELECT default_DOT_us_states.state_abbr, + default_DOT_us_states.state_id, + default_DOT_us_states.state_name + FROM roads.us_states AS default_DOT_us_states LEFT JOIN roads.us_region AS default_DOT_us_region ON default_DOT_us_states.state_region = default_DOT_us_region.us_region_description) + AS default_DOT_us_state ON default_DOT_hard_hat.state = default_DOT_us_state.state_abbr + GROUP BY default_DOT_us_state.state_abbr, default_DOT_us_state.state_id, default_DOT_us_state.state_name + )SELECT m0_default_DOT_num_repair_orders.default_DOT_num_repair_orders, + m1_default_DOT_avg_repair_price.default_DOT_avg_repair_price, + m2_default_DOT_total_repair_cost.default_DOT_total_repair_cost, + COALESCE(m0_default_DOT_num_repair_orders.state_abbr, m1_default_DOT_avg_repair_price.state_abbr, m2_default_DOT_total_repair_cost.state_abbr) state_abbr, + COALESCE(m0_default_DOT_num_repair_orders.state_id, m1_default_DOT_avg_repair_price.state_id, m2_default_DOT_total_repair_cost.state_id) state_id, + COALESCE(m0_default_DOT_num_repair_orders.state_name, m1_default_DOT_avg_repair_price.state_name, m2_default_DOT_total_repair_cost.state_name) state_name + FROM m0_default_DOT_num_repair_orders FULL OUTER JOIN m1_default_DOT_avg_repair_price ON m0_default_DOT_num_repair_orders.state_abbr = m1_default_DOT_avg_repair_price.state_abbr AND m0_default_DOT_num_repair_orders.state_id = m1_default_DOT_avg_repair_price.state_id AND m0_default_DOT_num_repair_orders.state_name = m1_default_DOT_avg_repair_price.state_name + FULL OUTER JOIN m2_default_DOT_total_repair_cost ON m0_default_DOT_num_repair_orders.state_abbr = m2_default_DOT_total_repair_cost.state_abbr AND m0_default_DOT_num_repair_orders.state_id = m2_default_DOT_total_repair_cost.state_id AND m0_default_DOT_num_repair_orders.state_name = m2_default_DOT_total_repair_cost.state_name + `.replace(/\s+/g, '') + ) + + const data = await dj.data.get( + [ + 'default.num_repair_orders', + 'default.avg_repair_price', + 'default.total_repair_cost', + ], + [ + 'default.us_state.state_abbr', + 'default.us_state.state_id', + 'default.us_state.state_name', + ], + [] + ) + + expect(data).toEqual({ + columns: [ + { + name: 'default_DOT_num_repair_orders', + type: 'bigint', + }, + { + name: 'default_DOT_avg_repair_price', + type: 'double', + }, + { + name: 'default_DOT_total_repair_cost', + type: 'double', + }, + { + name: 'state_abbr', + type: 'string', + }, + { + name: 'state_id', + type: 'int', + }, + { + name: 'state_name', + type: 'string', + }, + ], + data: [ + [486, 65682.0, 31921452.0, 'AZ', 3, 'Arizona'], + [486, 39301.5, 19100529.0, 'CT', 7, 'Connecticut'], + [729, 65595.66666666667, 47819241.0, 'GA', 11, 'Georgia'], + [729, 76555.33333333333, 55808838.0, 'MA', 22, 'Massachusetts'], + [1215, 64190.6, 77991579.0, 'MI', 23, 'Michigan'], + [972, 54672.75, 53141913.0, 'NJ', 31, 'New Jersey'], + [243, 53374.0, 12969882.0, 'NY', 33, 'New York'], + [243, 70418.0, 17111574.0, 'OK', 37, 'Oklahoma'], + [972, 54083.5, 52569162.0, 'PA', 39, 'Pennsylvania'], + ], + }) + + // Create a transform 2 downstream from a transform 1 + await dj.transforms.create({ + name: `${namespace}.repair_orders_w_hard_hats`, + description: 'Repair orders that have a hard hat assigned', + mode: 'published', + query: `SELECT + repair_order_id, + municipality_id, + hard_hat_id, + dispatcher_id + FROM ${namespace}.repair_orders_w_dispatchers + WHERE hard_hat_id IS NOT NULL`, + }) + + // Get transform 2 that's downstream from transform 1 and make sure it's valid + const transform2 = await dj.nodes.get( + `${namespace}.repair_orders_w_hard_hats` + ) + expect(transform2.name).toBe(`${namespace}.repair_orders_w_hard_hats`) + expect(transform2.status).toBe('valid') + expect(transform2.mode).toBe('published') + + // Create a draft transform 4 that's downstream from a not yet created transform 3 + await dj.transforms.create({ + name: `${namespace}.repair_orders_w_repair_order_id`, + description: 'Repair orders without a null ID', + mode: 'draft', + query: `SELECT + repair_order_id, + municipality_id, + hard_hat_id, + dispatcher_id + FROM ${namespace}.repair_orders_w_municipalities + WHERE repair_order_id IS NOT NULL`, + }) + const transform4 = await dj.nodes.get( + `${namespace}.repair_orders_w_repair_order_id` + ) + // Check that transform 4 is invalid because transform 3 does not exist + expect(transform4.name).toBe(`${namespace}.repair_orders_w_repair_order_id`) + expect(transform4.status).toBe('invalid') + expect(transform4.mode).toBe('draft') + + // Create a draft transform 3 that's downstream from transform 2 + await dj.transforms.create({ + name: `${namespace}.repair_orders_w_municipalities`, + description: 'Repair orders that have a municipality listed', + mode: 'draft', + query: `SELECT + repair_order_id, + municipality_id, + hard_hat_id, + dispatcher_id + FROM ${namespace}.repair_orders_w_hard_hats + WHERE municipality_id IS NOT NULL`, + }) + const transform3 = await dj.nodes.get( + `${namespace}.repair_orders_w_municipalities` + ) + expect(transform3.name).toBe(`${namespace}.repair_orders_w_municipalities`) + expect(transform3.status).toBe('valid') + expect(transform3.mode).toBe('draft') + + // Check that transform 4 is now valid after transform 3 was created + const transform4_now_valid = await dj.nodes.get( + `${namespace}.repair_orders_w_repair_order_id` + ) + expect(transform4_now_valid.status).toBe('valid') + + // Check that publishing transform 3 works + await dj.nodes.publish(transform3.name) +}, 60000) diff --git a/datajunction-clients/javascript/webpack.config.js b/datajunction-clients/javascript/webpack.config.js new file mode 100644 index 000000000..ce3b2ef43 --- /dev/null +++ b/datajunction-clients/javascript/webpack.config.js @@ -0,0 +1,15 @@ +const path = require('path') + +module.exports = { + entry: './src/index.js', + mode: 'production', + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'datajunction.js', + globalObject: 'this', + library: { + name: 'datajunction', + type: 'umd', + }, + }, +} diff --git a/datajunction-clients/python/.coveragerc b/datajunction-clients/python/.coveragerc new file mode 100644 index 000000000..fc5f1571b --- /dev/null +++ b/datajunction-clients/python/.coveragerc @@ -0,0 +1,28 @@ +# .coveragerc to control coverage.py +[run] +branch = True +source = djclient +# omit = bad_file.py + +[paths] +source = + src/ + */site-packages/ + +[report] +# Regexes for lines to exclude from consideration +exclude_lines = + # Have to re-enable the standard pragma + pragma: no cover + + # Don't complain about missing debug-only code: + def __repr__ + if self\.debug + + # Don't complain if tests don't hit defensive assertion code: + raise AssertionError + raise NotImplementedError + + # Don't complain if non-runnable code isn't run: + if 0: + if __name__ == .__main__.: diff --git a/datajunction-clients/python/.gitignore b/datajunction-clients/python/.gitignore new file mode 100644 index 000000000..43c7dc4ea --- /dev/null +++ b/datajunction-clients/python/.gitignore @@ -0,0 +1,55 @@ +# Temporary and binary files +*~ +*.py[cod] +*.so +*.cfg +!.isort.cfg +!setup.cfg +*.orig +*.log +*.pot +__pycache__/* +.cache/* +.*.swp +*/.ipynb_checkpoints/* +.DS_Store + +# Project files +.ropeproject +.project +.pydevproject +.settings +.idea +.vscode +tags + +# Package files +*.egg +*.eggs/ +.installed.cfg +*.egg-info + +# Unittest and coverage +htmlcov/* +.coverage +.coverage.* +.tox +junit*.xml +coverage.xml +.pytest_cache/ + +# Build and docs folder/files +build/* +dist/* +sdist/* +docs/api/* +docs/_rst/* +docs/_build/* +cover/* +MANIFEST + +# Per-project virtualenvs +.venv*/ +.conda*/ +.python-version +.pdm-python diff --git a/datajunction-clients/python/.isort.cfg b/datajunction-clients/python/.isort.cfg new file mode 100644 index 000000000..c8a204d9c --- /dev/null +++ b/datajunction-clients/python/.isort.cfg @@ -0,0 +1,3 @@ +[settings] +profile = black +known_first_party = datajunction diff --git a/datajunction-clients/python/.pre-commit-config.yaml b/datajunction-clients/python/.pre-commit-config.yaml new file mode 100644 index 000000000..6f32792cf --- /dev/null +++ b/datajunction-clients/python/.pre-commit-config.yaml @@ -0,0 +1,61 @@ +files: ^datajunction-clients/python/ + +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.1.0 + hooks: + - id: trailing-whitespace + - id: check-ast + - id: check-json + - id: check-merge-conflict + - id: check-xml + - id: check-yaml + - id: debug-statements + - id: end-of-file-fixer + - id: requirements-txt-fixer + - id: mixed-line-ending + args: ['--fix=auto'] # replace 'auto' with 'lf' to enforce Linux/Mac line endings or 'crlf' for Windows +- repo: https://github.com/pycqa/isort + rev: 5.12.0 + hooks: + - id: isort +- repo: https://github.com/psf/black + rev: 22.8.0 + hooks: + - id: black + language_version: python3 +- repo: https://github.com/PyCQA/flake8 + rev: 3.9.2 + hooks: + - id: flake8 +- repo: https://github.com/pre-commit/mirrors-mypy + rev: 'v0.931' # Use the sha / tag you want to point at + hooks: + - id: mypy + additional_dependencies: + - types-requests + - types-freezegun + - types-python-dateutil + - types-pkg_resources + - types-PyYAML + - types-tabulate +- repo: https://github.com/asottile/add-trailing-comma + rev: v2.2.1 + hooks: + - id: add-trailing-comma +- repo: https://github.com/pycqa/isort + rev: 5.12.0 + hooks: + - id: isort +- repo: https://github.com/hadialqattan/pycln + rev: v2.1.7 # Possible releases: https://github.com/hadialqattan/pycln/tags + hooks: + - id: pycln + args: [--config=pyproject.toml] +- repo: local + hooks: + - id: pylint + name: pylint + entry: pylint --disable=duplicate-code,use-implicit-booleaness-not-comparison,wrong-import-order + language: system + types: [python] diff --git a/datajunction-clients/python/LICENSE.txt b/datajunction-clients/python/LICENSE.txt new file mode 100644 index 000000000..c4b4b0a2e --- /dev/null +++ b/datajunction-clients/python/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2021 Beto Dealmeida + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/datajunction-clients/python/Makefile b/datajunction-clients/python/Makefile new file mode 100644 index 000000000..20012a12b --- /dev/null +++ b/datajunction-clients/python/Makefile @@ -0,0 +1,17 @@ +check: + pdm run pre-commit run --all-files + +test: + pdm run pytest --cov=datajunction --cov-report=html -vv tests/ --doctest-modules datajunction --without-integration --without-slow-integration ${PYTEST_ARGS} + +version: + @poetry version $(v) + @git add pyproject.toml + @git commit -m "v$$(poetry version -s)" + @git tag v$$(poetry version -s) + @git push + @git push --tags + @poetry version + +release: + @poetry publish --build diff --git a/datajunction-clients/python/README.md b/datajunction-clients/python/README.md new file mode 100644 index 000000000..c417444e8 --- /dev/null +++ b/datajunction-clients/python/README.md @@ -0,0 +1,396 @@ +# DataJunction Python Client + +This is a short introduction into the Python version of the DataJunction (DJ) client. +For a full comprehensive intro into the DJ functionality please check out [datajunction.io](https://datajunction.io/). + +## Installation + +To install: +``` +pip install datajunction +``` + +## Intro + +We have three top level client classes that help you choose the right path for your DataJunction actions. + +1. `DJClient` for basic read only access to metrics, dimensions, SQL and data. +2. `DJBuilder` for those who would like to modify their DJ data model, build new nodes and/or modify the existing ones. +3. `DJAdmin` for the administrators of the system to define the connections to your data catalog and engines. + +## DJ Client : Basic Access + +Here you can see how to access and use the most common DataJunction features. + +### Examples + +To initialize the client: + +```python +from datajunction import DJClient + +dj = DJClient("http://localhost:8000") +``` + +**NOTE** +If you are running in our demo docker environment please change the above URL to "http://dj:8000". + +You are now connected to your DJ service and you can start looking around. Let's see what namespaces we have in the system: + +```python +dj.list_namespaces() + +['default'] +``` + +Next let's see what metrics and dimensions exist in the `default` namespace: + +```python +dj.list_metrics(namespace="default") + +['default.num_repair_orders', + 'default.avg_repair_price', + 'default.total_repair_cost', + 'default.avg_length_of_employment', + 'default.total_repair_order_discounts', + 'default.avg_repair_order_discounts', + 'default.avg_time_to_dispatch'] + +dj.list_dimensions(namespace="default") + +['default.date_dim', + 'default.repair_order', + 'default.contractor', + 'default.hard_hat', + 'default.local_hard_hats', + 'default.us_state', + 'default.dispatcher', + 'default.municipality_dim'] +``` + +Now let's pick two metrics and see what dimensions they have in common: + +```python +dj.common_dimensions( + metrics=["default.num_repair_orders", "default.total_repair_order_discounts"], + name_only=True +) + +['default.dispatcher.company_name', + 'default.dispatcher.dispatcher_id', + 'default.dispatcher.phone', + 'default.hard_hat.address', + 'default.hard_hat.birth_date', + 'default.hard_hat.city', + ... +``` + +And finally let's ask DJ to show us some data for these metrics and some dimensions: + +```python +dj.data( + metrics=["default.num_repair_orders", "default.total_repair_order_discounts"], + dimensions=["default.hard_hat.city"] +) + +| default_DOT_num_repair_orders | default_DOT_total_repair_order_discounts | city | +| ----------------------------- | ---------------------------------------- | ----------- | +| 4 | 5475.110138 | Jersey City | +| 3 | 11483.300049 | Billerica | +| 5 | 6725.170074 | Southgate | +... +``` + +### Reference + +List of all available DJ client methods: + +- DJClient: + + ### list + - list_namespaces( prefix: Optional[str]) + - list_dimensions( namespace: Optional[str]) + - list_metrics( namespace: Optional[str]) + - list_cubes( namespace: Optional[str]) + - list_sources( namespace: Optional[str]) + - list_transforms( namespace: Optional[str]) + - list_nodes( namespace: Optional[str], type_: Optional[NodeType]) + + - list_catalogs() + - list_engines() + + ### find + - common_dimensions( metrics: List[str], name_only: bool = False) + - common_metrics( dimensions: List[str], name_only: bool = False) + + ### execute + - sql( metrics: List[str], + dimensions: Optional[List[str]], + filters: Optional[List[str]], + engine_name: Optional[str], + engine_version: Optional[str]) + - data( metrics: List[str], + dimensions: Optional[List[str]], + filters: Optional[List[str]], + engine_name: Optional[str], + engine_version: Optional[str], + async_: bool = True) + +## DJ Builder : Data Modelling + +In this section we'll show you few examples to modify the DJ data model and its nodes. + +### Start Here + +To initialize the DJ builder: + +```python +from datajunction import DJBuilder + +djbuilder = DJBuilder("http://localhost:8000") +``` + +**NOTE** +If you are running in our demo docker container please change the above URL to "http://dj:8000". + +### Namespaces + +To access a namespace or check if it exists you can use the same simple call: + +```python +djbuilder.namespace("default") + +Namespace(dj_client=..., namespace='default') +``` +```python +djbuilder.namespace("foo") + +[DJClientException]: Namespace `foo` does not exist. +``` + +To create a namespace: + +```python +djbuilder.create_namespace("foo") + +Namespace(dj_client=..., namespace='foo') +``` + +To delete (or restore) a namespace: + +```python +djbuilder.delete_namespace("foo") + +djbuilder.restore_namespace("foo") +``` + +**NOTE:** +The `cascade` parameter in both of above methods allows for cascading +effect applied to all underlying nodes and namespaces. Use it with caution! + +### Nodes + +To learn what **Node** means in the context of DJ, please check out [this datajuntion.io page](https://datajunction.io/docs/0.1.0/dj-concepts/nodes/). + +To list all (or some) nodes in the system you can use the `list_()` methods described +in the **DJ Client : Basic Access** section or you can use the namespace based method: + +All nodes for a given namespace can be found with: +```python +djbuilder.namespace("default").nodes() +``` + +Specific node types can be retrieved with: +```python +djbuilder.namespace("default").sources() +djbuilder.namespace("default").dimensions() +djbuilder.namespace("default").metrics() +djbuilder.namespace("default").transforms() +djbuilder.namespace("default").cubes() +``` + +To create a source node: + +```python +repair_orders = djbuilder.create_source( + name="repair_orders", + display_name="Repair Orders", + description="Repair orders", + catalog="dj", + schema_="roads", + table="repair_orders", +) +``` + +Nodes can also be created in draft mode: + +```python +repair_orders = djbuilder.create_source( + ..., + mode=NodeMode.DRAFT +) +``` + +To create a dimension node: + +```python +repair_order = djbuilder.create_dimension( + name="default.repair_order_dim", + query=""" + SELECT + repair_order_id, + municipality_id, + hard_hat_id, + dispatcher_id + FROM default.repair_orders + """, + description="Repair order dimension", + primary_key=["repair_order_id"], +) +``` + +To create a transform node: +```python +large_revenue_payments_only = djbuilder.create_transform( + name="default.large_revenue_payments_only", + query=""" + SELECT + payment_id, + payment_amount, + customer_id, + account_type + FROM default.revenue + WHERE payment_amount > 1000000 + """, + description="Only large revenue payments", +) +``` + +To create a metric: +```python +num_repair_orders = djbuilder.create_metric( + name="default.num_repair_orders", + query=""" + SELECT + count(repair_order_id) + FROM repair_orders + """, + description="Number of repair orders", +) +``` + +### Reference + +List of all available DJ builder methods: + +- DJBuilder: + + ### namespaces + - namespace( namespace: str) + - create_namespace( namespace: str) + - delete_namespace(self, namespace: str, cascade: bool = False) + - restore_namespace(self, namespace: str, cascade: bool = False) + + ### nodes + - delete_node(self, node_name: str) + - restore_node(self, node_name: str) + + ### nodes: source + - source(self, node_name: str) + - create_source( ..., mode: Optional[NodeMode] = NodeMode.PUBLISHED) + - register_table( catalog: str, schema: str, table: str) + + ### nodes: transform + - transform(self, node_name: str) + - create_transform( ..., mode: Optional[NodeMode] = NodeMode.PUBLISHED) + + ### nodes: dimension + - dimension(self, node_name: str) + - create_dimension( ..., mode: Optional[NodeMode] = NodeMode.PUBLISHED) + + ### nodes: metric + - metric(self, node_name: str) + - create_metric( ..., mode: Optional[NodeMode] = NodeMode.PUBLISHED) + + ### nodes: cube + - cube(self, node_name: str) + - create_cube( ..., mode: Optional[NodeMode] = NodeMode.PUBLISHED) + + +## DJ System Administration + +In this section we'll describe how to manage your catalog and engines. + +### Start Here + +To initialize the DJ admin: + +```python +from datajunction import DJAdmin + +djadmin = DJAdmin("http://localhost:8000") +``` + +**NOTE** +If you are running in our demo docker container please change the above URL to "http://dj:8000". + +### Examples + +To list available catalogs: + +```python +djadmin.list_catalogs() + +['warehouse'] +``` + +To list available engines: + +```python +djadmin.list_engines() + +[{'name': 'duckdb', 'version': '0.7.1'}] +``` + +To create a catalog: + +```python +djadmin.add_catalog(name="my-new-catalog") +``` + +To create a new engine: + +```python +djadmin.add_engine( + name="Spark", + version="3.2.1", + uri="http:/foo", + dialect="spark" +) +``` + +To linke an engine to a catalog: +```python +djadmin.link_engine_to_catalog( + engine="Spark", version="3.2.1", catalog="my-new-catalog" +) +``` + +### Reference + +List of all available DJ builder methods: + +- DJAdmin: + + ### Catalogs + - list_catalogs() # in DJClient + - get_catalog( name: str) + - add_catalog( name: str) + + ### Engines + - list_engines() # in DJClient + - get_engine( name: str) + - add_engine( name: str,version: str, uri: Optional[str], dialect: Optional[str]) + + ### Together + - link_engine_to_catalog( engine_name: str, engine_version: str, catalog: str) diff --git a/datajunction-clients/python/datajunction/__about__.py b/datajunction-clients/python/datajunction/__about__.py new file mode 100644 index 000000000..9d2f03de2 --- /dev/null +++ b/datajunction-clients/python/datajunction/__about__.py @@ -0,0 +1,4 @@ +""" +Version for Hatch +""" +__version__ = "0.0.1a15" diff --git a/datajunction-clients/python/datajunction/__init__.py b/datajunction-clients/python/datajunction/__init__.py new file mode 100644 index 000000000..96cce7c83 --- /dev/null +++ b/datajunction-clients/python/datajunction/__init__.py @@ -0,0 +1,52 @@ +""" +A DataJunction client for connecting to a DataJunction server +""" +from importlib.metadata import PackageNotFoundError, version # pragma: no cover + +from datajunction.admin import DJAdmin +from datajunction.builder import DJBuilder +from datajunction.client import DJClient +from datajunction.models import ( + AvailabilityState, + ColumnAttribute, + Engine, + MaterializationConfig, + NodeMode, +) +from datajunction.nodes import ( + Cube, + Dimension, + Metric, + Namespace, + Node, + Source, + Transform, +) + +try: + # Change here if project is renamed and does not equal the package name + DIST_NAME = __name__ + __version__ = version(DIST_NAME) +except PackageNotFoundError: # pragma: no cover + __version__ = "unknown" +finally: + del version, PackageNotFoundError + + +__all__ = [ + "DJClient", + "DJBuilder", + "DJAdmin", + "AvailabilityState", + "ColumnAttribute", + "Source", + "Dimension", + "Transform", + "MaterializationConfig", + "Metric", + "Cube", + "Node", + "NodeMode", + "Namespace", + "Engine", +] diff --git a/datajunction-clients/python/datajunction/_internal.py b/datajunction-clients/python/datajunction/_internal.py new file mode 100644 index 000000000..3ac374f9a --- /dev/null +++ b/datajunction-clients/python/datajunction/_internal.py @@ -0,0 +1,425 @@ +"""DataJunction base client setup.""" + +# pylint: disable=redefined-outer-name, import-outside-toplevel, too-many-lines +import logging +import platform +import warnings +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, TypedDict +from urllib.parse import urljoin + +try: + import pandas as pd +except ImportError: # pragma: no cover + warnings.warn( + ( + "Optional dependency `pandas` not found, data retrieval" + "disabled. You can install pandas by running `pip install pandas`." + ), + ImportWarning, + ) +import requests +from pydantic import BaseModel, Field +from requests.adapters import CaseInsensitiveDict, HTTPAdapter + +from datajunction import models +from datajunction.exceptions import DJClientException, DJNodeAlreadyExists + +if TYPE_CHECKING: + from datajunction.nodes import Node # pragma: no cover + +DEFAULT_NAMESPACE = "default" +_logger = logging.getLogger(__name__) + + +# +# Helpers +# +def from_jupyter() -> bool: # pragma: no cover + """ + Checks whether we're running from an IPython interactive console + """ + try: + from IPython import get_ipython + except ImportError: + return False + return get_ipython() is not None + + +class Results(TypedDict): + """ + Results in a completed DJ Query + """ + + columns: Tuple[str] + data: Tuple[Tuple] + + +class RequestsSessionWithEndpoint(requests.Session): # pragma: no cover + """ + Creates a requests session that comes with an endpoint that all + subsequent requests will use as a prefix. + """ + + def __init__(self, endpoint: str = None, show_traceback: bool = False): + super().__init__() + self.endpoint = endpoint + self.mount("http://", HTTPAdapter()) + self.mount("https://", HTTPAdapter()) + + self.headers = CaseInsensitiveDict( + { + "User-Agent": ( + f"datajunction;;N/A;" + f"{platform.processor() or platform.machine()};" + f"{platform.system()};" + f"{platform.release()} {platform.version()}" + ), + }, + ) + + self._show_traceback = show_traceback + + if from_jupyter() and not self._show_traceback: + from IPython import get_ipython # pylint: disable=import-error + + def shortened_error(*args, **kwargs): # pylint: disable=unused-argument + import sys + + etype, value, _ = sys.exc_info() + _logger.error("[%s]: %s", etype.__name__, value) + + get_ipython().showtraceback = shortened_error + + def request(self, method, url, *args, **kwargs): + """ + Make the request with the full URL. + """ + url = self.construct_url(url) + try: + response = super().request(method, url, *args, **kwargs) + response.raise_for_status() + return response + except requests.exceptions.RequestException as exc: + error_message = None + if not exc.response: + error_message = str(exc) + if exc.response.headers.get("Content-Type") == "application/json": + error_message = exc.response.json().get("message") + if not error_message: + error_message = ( + f"Request failed with status code {exc.response.status_code}" + ) + raise DJClientException(error_message) from exc + + def prepare_request(self, request, *args, **kwargs): + """ + Prepare the request with the full URL. + """ + request.url = self.construct_url(request.url) + return super().prepare_request( + request, + *args, + **kwargs, + ) + + def construct_url(self, url): + """ + Construct full URL based off the endpoint. + """ + return urljoin(self.endpoint, url) + + +# +# Main DJClient (internal) +# +class DJClient: + """ + Internal client class with non-user facing methods. + """ + + def __init__( # pylint: disable=too-many-arguments + self, + uri: str = "http://localhost:8000", + engine_name: str = None, + engine_version: str = None, + requests_session: RequestsSessionWithEndpoint = None, + target_namespace: str = DEFAULT_NAMESPACE, + timeout: int = 2 * 60, + debug: bool = False, + ): + self.target_namespace = target_namespace + self.uri = uri + self.engine_name = engine_name + self.engine_version = engine_version + self._debug = debug + + if not requests_session: # pragma: no cover + self._session = RequestsSessionWithEndpoint( + endpoint=self.uri, + show_traceback=self._debug, + ) + else: # pragma: no cover + self._session = requests_session + self._timeout = timeout + + @staticmethod + def _primary_key_from_columns(columns) -> List[str]: + """ + Extracts the primary key from the columns + """ + return [ + column["name"] + for column in columns + if any( + attr["attribute_type"]["name"] == "primary_key" + for attr in column["attributes"] + if attr + ) + ] + + @staticmethod + def process_results(results) -> "pd.DataFrame": + """ + Return a pandas dataframe of the results if pandas is installed + """ + if "results" in results and results["results"]: + columns = results["results"][0]["columns"] + rows = results["results"][0]["rows"] + try: + return pd.DataFrame( + rows, + columns=[col["name"] for col in columns], + ) + except NameError: # pragma: no cover + return Results( + data=rows, + columns=tuple(col["name"] for col in columns), # type: ignore + ) + raise DJClientException("No data for query!") + + # + # Node methods + # + def _get_nodes_in_namespace( + self, + namespace: str, + type_: Optional[models.NodeType] = None, + ): + """ + Retrieves all nodes in given namespace. + """ + response = self._session.get( + f"/namespaces/{namespace}/" + (f"?type_={type_.value}" if type_ else ""), + ) + return response.json() + + def _get_all_nodes( + self, + type_: Optional[models.NodeType] = None, + ): + """ + Retrieve all nodes of a given type. + """ + response = self._session.get( + "/nodes/" + (f"?node_type={type_.value}" if type_ else ""), + ) + return response.json() + + def _verify_node_exists(self, node_name: str, type_: str) -> Dict[str, Any]: + """ + Retrieves a node and verifies that it exists and has the expected node type. + """ + node = self._get_node(node_name) + if "name" not in node: + raise DJClientException(f"No node with name {node_name} exists!") + if "name" in node and node["type"] != type_: + raise DJClientException( + f"A node with name {node_name} exists, but it is not a {type_} node!", + ) + return node + + def _validate_node(self, node: "Node"): + """ + Check if a locally defined node is valid + """ + node_copy = node.dict().copy() + node_copy["mode"] = models.NodeMode.PUBLISHED + response = self._session.post( + "/nodes/validate/", + json=node_copy, + timeout=self._timeout, + ) + return response.json() + + def _create_node( + self, + node: "Node", + mode: Optional[models.NodeMode] = models.NodeMode.PUBLISHED, + ): + """ + Helper function to create a node. + Raises an error if node already exsist and is active. + """ + existing_node = self._get_node(node_name=node.name) + if "name" in existing_node: + raise DJNodeAlreadyExists(node_name=node.name) + node.mode = mode + response = self._session.post( + f"/nodes/{node.type}/", + timeout=self._timeout, + json=node.dict(exclude_none=True, exclude={"type"}), + ) + return response + + def _update_node(self, node_name: str, update_input: models.UpdateNode): + """ + Call node update API with attributes to update. + """ + return self._session.patch(f"/nodes/{node_name}/", json=update_input.dict()) + + def _publish_node(self, node_name: str, update_input: models.UpdateNode): + """ + Retrieves a node. + """ + response = self._session.patch(f"/nodes/{node_name}/", json=update_input.dict()) + return response.json() + + def _get_node(self, node_name: str): + """ + Retrieves a node. + """ + try: + response = self._session.get(f"/nodes/{node_name}/") + return response.json() + except DJClientException as exc: # pragma: no cover + return exc.__dict__ + + def _get_cube(self, node_name: str): + """ + Retrieves a Cube node. + """ + response = self._session.get(f"/cubes/{node_name}/") + return response.json() + + def get_metric(self, node_name: str): + """ + Helper function to retrieve metadata for the given metric node. + """ + response = self._session.get(f"/metrics/{node_name}/") + return response.json() + + def _get_node_revisions(self, node_name: str): + """ + Retrieve all revisions of the node + """ + response = self._session.get(f"/nodes/{node_name}/revisions") + return response.json() + + def _link_dimension_to_node( + self, + node_name: str, + column_name: str, + dimension_name: str, + dimension_column: Optional[str] = None, + ): + """ + Helper function to link a dimension to the node. + """ + response = self._session.post( + f"/nodes/{node_name}/columns/{column_name}/" + f"?dimension={dimension_name}&dimension_column={dimension_column}", + timeout=self._timeout, + ) + return response.json() + + def _unlink_dimension_from_node( + self, + node_name: str, + column_name: str, + dimension_name: str, + dimension_column: Optional[str] = None, + ): + """ + Helper function to un-link a dimension to the node. + """ + response = self._session.delete( + f"/nodes/{node_name}/columns/{column_name}/" + f"?dimension={dimension_name}&dimension_column={dimension_column}", + timeout=self._timeout, + ) + return response.json() + + def _upsert_materialization( + self, + node_name: str, + config: models.MaterializationConfig, + ): + """ + Upserts a materialization config for the node. + """ + response = self._session.post( + f"/nodes/{node_name}/materialization/", + json=config.dict(), + ) + return response.json() + + def _deactivate_materialization( + self, + node_name: str, + materialization_name: str, + ): + """ + Upserts a materialization config for the node. + """ + response = self._session.delete( + f"/nodes/{node_name}/materializations/", + params={ + "materialization_name": materialization_name, + }, + ) + return response.json() + + def _add_availability_state( + self, + node_name: str, + availability: models.AvailabilityState, + ): + """ + Adds an availability state for the node + """ + response = self._session.post( + f"/data/{node_name}/availability/", + json=availability.dict(), + ) + return response.json() + + def _set_column_attributes( + self, + node_name, + attributes: List[models.ColumnAttribute], + ): + """ + Sets attributes for columns on the node + """ + response = self._session.post( + f"/nodes/{node_name}/attributes/", + json=[attribute.dict() for attribute in attributes], + ) + return response.json() + + +class ClientEntity(BaseModel): + """ + Any entity that uses the DJ client. + """ + + dj_client: DJClient = Field(exclude=True) + + class Config: # pylint: disable=too-few-public-methods + """ + Allow arbitrary types to support DJClient but exclude + it from the output. + """ + + arbitrary_types_allowed = True + exclude = {"dj_client"} diff --git a/datajunction-clients/python/datajunction/admin.py b/datajunction-clients/python/datajunction/admin.py new file mode 100644 index 000000000..a257bbbc5 --- /dev/null +++ b/datajunction-clients/python/datajunction/admin.py @@ -0,0 +1,102 @@ +"""DataJunction main client module.""" + +from typing import Optional + +from datajunction.builder import DJBuilder +from datajunction.exceptions import DJClientException + + +class DJAdmin(DJBuilder): # pylint: disable=too-many-public-methods + """ + Client class for DJ system administration. + """ + + # + # Data Catalogs + # + def get_catalog(self, name: str) -> dict: + """ + Get catalog by name. + """ + response = self._session.get(f"/catalogs/{name}/", timeout=self._timeout) + return response.json() + + def add_catalog(self, name: str) -> None: + """ + Add a catalog. + """ + response = self._session.post( + "/catalogs/", + json={"name": f"{name}"}, + timeout=self._timeout, + ) + json_response = response.json() + if not response.ok: + raise DJClientException( + f"Adding catalog `{name}` failed: {json_response}", + ) # pragma: no cover + + # + # Database Engines + # + def get_engine(self, name: str, version: str) -> dict: + """ + Get engine by name. + """ + response = self._session.get( + f"/engines/{name}/{version}", + timeout=self._timeout, + ) + return response.json() + + def add_engine( + self, + name: str, + version: str, + uri: Optional[str], + dialect: Optional[str], + ) -> None: + """ + Add an engine. + """ + response = self._session.post( + "/engines/", + json={ + "name": f"{name}", + "version": f"{version}", + "uri": f"{uri}", + "dialect": f"{dialect}", + }, + timeout=self._timeout, + ) + json_response = response.json() + if not response.ok: + raise DJClientException( + f"Adding engine failed: {json_response}", + ) # pragma: no cover + + def link_engine_to_catalog( + self, + engine: str, + version: str, + catalog: str, + ) -> None: + """ + Add/link a particular engine to a particular catalog. + """ + response = self._session.post( + f"/catalogs/{catalog}/engines/", + json=[ + { + "name": f"{engine}", + "version": f"{version}", + }, + ], + timeout=self._timeout, + ) + json_response = response.json() + if not response.ok: + raise DJClientException( + f"Linking engine (name: {engine}, version: {version}) " + f"to catalog `{catalog}` failed: {json_response}", + ) # pragma: no cover diff --git a/datajunction-clients/python/datajunction/builder.py b/datajunction-clients/python/datajunction/builder.py new file mode 100644 index 000000000..3120e2a0e --- /dev/null +++ b/datajunction-clients/python/datajunction/builder.py @@ -0,0 +1,341 @@ +"""DataJunction main client module.""" + +from http import HTTPStatus +from typing import List, Optional + +from datajunction import models +from datajunction.client import DJClient +from datajunction.exceptions import DJClientException, DJNamespaceAlreadyExists +from datajunction.nodes import Cube, Dimension, Metric, Namespace, Source, Transform + + +class DJBuilder(DJClient): # pylint: disable=too-many-public-methods + """ + Client class for DJ dag and node modifications. + """ + + # + # Namespace + # + def namespace(self, namespace: str) -> "Namespace": + """ + Returns the specified node namespace. + """ + namespaces = self.list_namespaces(prefix=namespace) + if namespace not in namespaces: + raise DJClientException(f"Namespace `{namespace}` does not exist.") + return Namespace(namespace=namespace, dj_client=self) + + def create_namespace(self, namespace: str) -> "Namespace": + """ + Create a namespace with a given name. + """ + response = self._session.post( + f"/namespaces/{namespace}/", + timeout=self._timeout, + ) + json_response = response.json() + if response.status_code == 409: + raise DJNamespaceAlreadyExists(json_response["message"]) + return Namespace(namespace=namespace, dj_client=self) + + def delete_namespace(self, namespace: str, cascade: bool = False) -> None: + """ + Delete a namespace by name. + """ + response = self._session.delete( + f"/namespaces/{namespace}/", + timeout=self._timeout, + params={ + "cascade": cascade, + }, + ) + if response.status_code != HTTPStatus.OK: + raise DJClientException(response.json()["message"]) + + def restore_namespace(self, namespace: str, cascade: bool = False) -> None: + """ + Restore a namespace by name. + """ + response = self._session.post( + f"/namespaces/{namespace}/restore/", + timeout=self._timeout, + params={ + "cascade": cascade, + }, + ) + if response.status_code != HTTPStatus.CREATED: + raise DJClientException(response.json()["message"]) + + # + # Nodes: all + # + def delete_node(self, node_name: str) -> None: + """ + Delete (aka deactivate) this node. + """ + response = self._session.delete( + f"/nodes/{node_name}/", + timeout=self._timeout, + ) + json_response = response.json() + if not response.ok: + raise DJClientException( + f"Deleting node `{node_name}` failed: {json_response}", + ) # pragma: no cover + + def restore_node(self, node_name: str) -> None: + """ + Restore (aka reactivate) this node. + """ + response = self._session.post( + f"/nodes/{node_name}/restore/", + timeout=self._timeout, + ) + json_response = response.json() + if not response.ok: + raise DJClientException( + f"Restoring node `{node_name}` failed: {json_response}", + ) # pragma: no cover + + # + # Nodes: SOURCE + # + def source(self, node_name: str) -> "Source": + """ + Retrieves a source node with that name if one exists. + """ + node_dict = self._verify_node_exists( + node_name, + type_=models.NodeType.SOURCE.value, + ) + node = Source( + **node_dict, + dj_client=self, + ) + node.primary_key = self._primary_key_from_columns(node_dict["columns"]) + return node + + def create_source( # pylint: disable=too-many-arguments + self, + name: str, + catalog: str, + schema_: str, + table: str, + description: Optional[str] = None, + display_name: Optional[str] = None, + columns: Optional[List[models.Column]] = None, + primary_key: Optional[List[str]] = None, + tags: Optional[List[models.Tag]] = None, + mode: Optional[models.NodeMode] = models.NodeMode.PUBLISHED, + ) -> "Source": + """ + Creates a new Source node with given parameters. + """ + new_node = Source( + dj_client=self, + name=name, + description=description, + display_name=display_name, + tags=tags, + primary_key=primary_key, + catalog=catalog, + schema_=schema_, + table=table, + columns=columns, + ) + self._create_node(node=new_node, mode=mode) + return new_node + + def register_table(self, catalog: str, schema: str, table: str) -> Source: + """ + Register a table as a source node. This will create a source node under the configured + `source_node_namespace` (a server-side setting), which defaults to the `source` namespace. + """ + response = self._session.post(f"/register/table/{catalog}/{schema}/{table}/") + new_node = Source( + **response.json(), + dj_client=self, + ) + return new_node + + # + # Nodes: TRANSFORM + # + def transform(self, node_name: str) -> "Transform": + """ + Retrieves a transform node with that name if one exists. + """ + node_dict = self._verify_node_exists( + node_name, + type_=models.NodeType.TRANSFORM.value, + ) + node = Transform( + **node_dict, + dj_client=self, + ) + node.primary_key = self._primary_key_from_columns(node_dict["columns"]) + return node + + def create_transform( # pylint: disable=too-many-arguments + self, + name: str, + query: str, + description: Optional[str] = None, + display_name: Optional[str] = None, + primary_key: Optional[List[str]] = None, + tags: Optional[List[models.Tag]] = None, + mode: Optional[models.NodeMode] = models.NodeMode.PUBLISHED, + ) -> "Transform": + """ + Creates a new Transform node with given parameters. + """ + new_node = Transform( + dj_client=self, + name=name, + description=description, + display_name=display_name, + tags=tags, + primary_key=primary_key, + query=query, + ) + self._create_node(node=new_node, mode=mode) + return new_node + + # + # Nodes: DIMENSION + # + def dimension(self, node_name: str) -> "Dimension": + """ + Retrieves a Dimension node with that name if one exists. + """ + node_dict = self._verify_node_exists( + node_name, + type_=models.NodeType.DIMENSION.value, + ) + node = Dimension( + **node_dict, + dj_client=self, + ) + node.primary_key = self._primary_key_from_columns(node_dict["columns"]) + return node + + def create_dimension( # pylint: disable=too-many-arguments + self, + name: str, + query: str, + primary_key: Optional[List[str]], + description: Optional[str] = None, + display_name: Optional[str] = None, + tags: Optional[List[models.Tag]] = None, + mode: Optional[models.NodeMode] = models.NodeMode.PUBLISHED, + ) -> "Transform": + """ + Creates a new Dimension node with given parameters. + """ + new_node = Dimension( + dj_client=self, + name=name, + description=description, + display_name=display_name, + tags=tags, + primary_key=primary_key, + query=query, + ) + self._create_node(node=new_node, mode=mode) + return new_node + + # + # Nodes: METRIC + # + def metric(self, node_name: str) -> "Metric": + """ + Retrieves a Metric node with that name if one exists. + """ + node_dict = self._verify_node_exists( + node_name, + type_=models.NodeType.METRIC.value, + ) + node = Metric( + **node_dict, + dj_client=self, + ) + node.primary_key = self._primary_key_from_columns(node_dict["columns"]) + return node + + def create_metric( # pylint: disable=too-many-arguments + self, + name: str, + query: str, + description: Optional[str] = None, + display_name: Optional[str] = None, + primary_key: Optional[List[str]] = None, + tags: Optional[List[models.Tag]] = None, + mode: Optional[models.NodeMode] = models.NodeMode.PUBLISHED, + ) -> "Transform": + """ + Creates a new Metric node with given parameters. + """ + new_node = Metric( + dj_client=self, + name=name, + description=description, + display_name=display_name, + tags=tags, + primary_key=primary_key, + query=query, + ) + self._create_node(node=new_node, mode=mode) + return new_node + + # + # Nodes: CUBE + # + def cube(self, node_name: str) -> "Cube": # pragma: no cover + """ + Retrieves a Cube node with that name if one exists. + """ + node_dict = self._get_cube(node_name) + if "name" not in node_dict: + raise DJClientException(f"Cube `{node_name}` does not exist") + dimensions = [ + f'{col["node_name"]}.{col["name"]}' + for col in node_dict["cube_elements"] + if col["type"] != "metric" + ] + metrics = [ + f'{col["node_name"]}.{col["name"]}' + for col in node_dict["cube_elements"] + if col["type"] == "metric" + ] + return Cube( + **node_dict, + metrics=metrics, + dimensions=dimensions, + dj_client=self, + ) + + def create_cube( # pylint: disable=too-many-arguments + self, + name: str, + metrics: List[str], + dimensions: List[str], + filters: Optional[List[str]] = None, + description: Optional[str] = None, + display_name: Optional[str] = None, + mode: Optional[models.NodeMode] = models.NodeMode.PUBLISHED, + ) -> "Cube": + """ + Instantiates a new cube with the given parameters. + """ + new_node = Cube( # pragma: no cover + dj_client=self, + name=name, + metrics=metrics, + dimensions=dimensions, + filters=filters, + description=description, + display_name=display_name, + ) + self._create_node(node=new_node, mode=mode) # pragma: no cover + return new_node # pragma: no cover diff --git a/datajunction-clients/python/datajunction/client.py b/datajunction-clients/python/datajunction/client.py new file mode 100644 index 000000000..7f4da3024 --- /dev/null +++ b/datajunction-clients/python/datajunction/client.py @@ -0,0 +1,282 @@ +"""DataJunction main client module.""" + +import time +from typing import List, Optional, Union +from urllib.parse import urlencode + +from alive_progress import alive_bar + +from datajunction import _internal, models +from datajunction.exceptions import DJClientException + + +class DJClient(_internal.DJClient): + """ + Client class for basic DJ dag and data access. + """ + + # + # List basic objects: namespaces, dimensions, metrics, cubes + # + def list_namespaces(self, prefix: Optional[str] = None) -> List[str]: + """ + List namespaces starting with a given prefix. + """ + namespaces = self._session.get("/namespaces/").json() + if prefix: + namespaces = [n for n in namespaces if n.startswith(prefix)] + return namespaces + + def list_dimensions(self, namespace: Optional[str] = None) -> List[str]: + """ + List dimension nodes for a given namespace or all. + """ + if namespace: + return self._get_nodes_in_namespace( + namespace=namespace, + type_=models.NodeType.DIMENSION, + ) + return self._get_all_nodes(type_=models.NodeType.DIMENSION) + + def list_metrics(self, namespace: Optional[str] = None) -> List[str]: + """ + List metric nodes for a given namespace or all. + """ + if namespace: + return self._get_nodes_in_namespace( + namespace=namespace, + type_=models.NodeType.METRIC, + ) + return self._get_all_nodes(type_=models.NodeType.METRIC) + + def list_cubes(self, namespace: Optional[str] = None) -> List[str]: + """ + List cube nodes for a given namespace or all. + """ + if namespace: + return self._get_nodes_in_namespace( + namespace=namespace, + type_=models.NodeType.CUBE, + ) + return self._get_all_nodes(type_=models.NodeType.CUBE) + + # + # List other nodes: sources, transforms, all. + # + def list_sources(self, namespace: Optional[str] = None) -> List[str]: + """ + List source nodes for a given namespace or all. + """ + if namespace: + return self._get_nodes_in_namespace( + namespace=namespace, + type_=models.NodeType.SOURCE, + ) + return self._get_all_nodes(type_=models.NodeType.SOURCE) + + def list_transforms(self, namespace: Optional[str] = None) -> List[str]: + """ + List transform nodes for a given namespace or all. + """ + if namespace: + return self._get_nodes_in_namespace( + namespace=namespace, + type_=models.NodeType.TRANSFORM, + ) + return self._get_all_nodes(type_=models.NodeType.TRANSFORM) + + def list_nodes( + self, + type_: Optional[models.NodeType] = None, + namespace: Optional[str] = None, + ) -> List[str]: + """ + List any nodes for a given node type and/or namespace. + """ + if namespace: + return self._get_nodes_in_namespace( + namespace=namespace, + type_=type_, + ) + return self._get_all_nodes(type_=type_) + + # + # Get common metrics and dimensions + # + def common_dimensions( + self, + metrics: List[str], + name_only: bool = False, + ) -> List[Union[str, dict]]: # pragma: no cover # Tested in integration tests + """ + Return common dimensions for a set of metrics. + """ + query_params = [] + for metric in metrics: + query_params.append((models.NodeType.METRIC.value, metric)) + json_response = self._session.get( + f"/metrics/common/dimensions/?{urlencode(query_params)}", + ).json() + if name_only: + return [dimension["name"] for dimension in json_response] + return json_response + + def common_metrics( + self, + dimensions: List[str], + name_only: bool = False, + ) -> List[Union[str, dict]]: # pragma: no cover # Tested in integration tests + """ + Return common metrics for a set of dimensions. + """ + query_params = [("node_type", models.NodeType.METRIC.value)] + for dim in dimensions: + query_params.append((models.NodeType.DIMENSION.value, dim)) + json_response = self._session.get( + f"/dimensions/common/?{urlencode(query_params)}", + ).json() + if name_only: + return [metric["name"] for metric in json_response] + return [ + { + "name": metric["name"], + "display_name": metric["display_name"], + "description": metric["description"], + "query": metric["query"], + # perhaps we should also provide `paths` like we do with common dimensions + } + for metric in json_response + ] + + # + # Get SQL + # + def sql( # pylint: disable=too-many-arguments + self, + metrics: List[str], + dimensions: Optional[List[str]] = None, + filters: Optional[List[str]] = None, + engine_name: Optional[str] = None, + engine_version: Optional[str] = None, + ): + """ + Builds SQL for one (or multiple) metrics with the provided dimensions and filters. + """ + if len(metrics) == 1: + response = self._session.get( + f"/sql/{metrics[0]}/", + params={ + "dimensions": dimensions or [], + "filters": filters or [], + "engine_name": engine_name or self.engine_name, + "engine_version": engine_version or self.engine_version, + }, + ) + else: + response = self._session.get( + "/sql/", + params={ + "metrics": metrics, + "dimensions": dimensions or [], + "filters": filters or [], + "engine_name": engine_name or self.engine_name, + "engine_version": engine_version or self.engine_version, + }, + ) + if response.status_code == 200: + return response.json()["sql"] + return response.json() + + # + # Get data + # + def data( # pylint: disable=too-many-arguments,too-many-locals + self, + metrics: List[str], + dimensions: Optional[List[str]] = None, + filters: Optional[List[str]] = None, + engine_name: Optional[str] = None, + engine_version: Optional[str] = None, + async_: bool = True, + ): + """ + Retrieves the data for the node with the provided dimensions and filters. + """ + printed_links = False + with alive_bar( + title="Processing", + length=20, + bar="smooth", + force_tty=True, + calibrate=5e40, + ) as progress_bar: + poll_interval = 1 # Initial polling interval in seconds + job_state = models.QueryState.UNKNOWN + results = None + while job_state not in models.END_JOB_STATES: + progress_bar() # pylint: disable=not-callable + response = self._session.get( + "/data/", + params={ + "metrics": metrics, + "dimensions": dimensions or [], + "filters": filters or [], + "engine_name": engine_name or self.engine_name, + "engine_version": engine_version or self.engine_version, + "async_": async_, + }, + ) + results = response.json() + + # Raise errors if any + if not response.ok: + raise DJClientException(f"Error retrieving data: {response.text}") + if results["state"] not in models.QueryState.list(): + raise DJClientException( # pragma: no cover + f"Query state {results['state']} is not a DJ-parseable query state!" + " Please reach out to your server admin to make sure DJ is configured" + " correctly.", + ) + + # Update the query state and print links if any + job_state = models.QueryState(results["state"]) + if not printed_links and results["links"]: # pragma: no cover + print( + "Links:\n" + + "\n".join([f"\t* {link}" for link in results["links"]]), + ) + printed_links = True + progress_bar.title = f"Status: {job_state.value}" + + # Update the polling interval + time.sleep(poll_interval) + poll_interval *= 2 + + # Return results if the job has finished + if job_state == models.QueryState.FINISHED: + return self.process_results(results) + if job_state == models.QueryState.CANCELED: # pragma: no cover + raise DJClientException("Query execution was canceled!") + raise DJClientException( # pragma: no cover + f"Error retrieving data: {response.text}", + ) + + # + # Data Catalog and Engines + # + def list_catalogs(self) -> List[str]: + """ + List all catalogs. + """ + json_response = self._session.get("/catalogs/", timeout=self._timeout).json() + return [catalog["name"] for catalog in json_response] + + def list_engines(self) -> List[dict]: + """ + List all engines. + """ + json_response = self._session.get("/engines/", timeout=self._timeout).json() + return [ + {"name": engine["name"], "version": engine["version"]} + for engine in json_response + ] diff --git a/datajunction-clients/python/datajunction/exceptions.py b/datajunction-clients/python/datajunction/exceptions.py new file mode 100644 index 000000000..b8298ef5d --- /dev/null +++ b/datajunction-clients/python/datajunction/exceptions.py @@ -0,0 +1,23 @@ +"""DJ client exceptions""" + + +class DJClientException(Exception): + """ + Base class for client errors. + """ + + +class DJNamespaceAlreadyExists(DJClientException): + """ + Raised when a namespace to be created already exists. + """ + + +class DJNodeAlreadyExists(DJClientException): + """ + Raised when a node to be created already exists. + """ + + def __init__(self, node_name: str, *args) -> None: + self.message = f"Node `{node_name}` already exists." + super().__init__(self.message, *args) diff --git a/datajunction-clients/python/datajunction/models.py b/datajunction-clients/python/datajunction/models.py new file mode 100644 index 000000000..efc438722 --- /dev/null +++ b/datajunction-clients/python/datajunction/models.py @@ -0,0 +1,151 @@ +"""Models used by the DJ client.""" +import enum +from typing import Dict, List, Optional + +from pydantic import BaseModel + + +class Engine(BaseModel): + """ + Represents an engine + """ + + name: str + version: Optional[str] + + +class MaterializationConfig(BaseModel): + """ + A node's materialization config + """ + + engine: Engine + schedule: str + config: Dict + + +class NodeMode(str, enum.Enum): + """ + DJ node's mode + """ + + DRAFT = "draft" + PUBLISHED = "published" + + +class NodeStatus(str, enum.Enum): + """ + DJ node's status + """ + + VALID = "valid" + INVALID = "invalid" + + +class NodeType(str, enum.Enum): + """ + DJ node types + """ + + METRIC = "metric" + DIMENSION = "dimension" + SOURCE = "source" + TRANSFORM = "transform" + CUBE = "cube" + + +class ColumnAttribute(BaseModel): + """ + Represents a column attribute + """ + + attribute_type_namespace: Optional[str] = "system" + attribute_type_name: str + column_name: str + + +class SourceColumn(BaseModel): + """ + A column used in creation of a source node + """ + + name: str + type: str + attributes: Optional[List[ColumnAttribute]] + dimension: Optional[str] + + +class UpdateNode(BaseModel): + """ + Fields for updating a node + """ + + display_name: Optional[str] + description: Optional[str] + mode: Optional[NodeMode] + primary_key: Optional[List[str]] + query: Optional[str] + + # source nodes only + catalog: Optional[str] + schema_: Optional[str] + table: Optional[str] + columns: Optional[List[SourceColumn]] = [] + + +class QueryState(str, enum.Enum): + """ + Different states of a query. + """ + + UNKNOWN = "UNKNOWN" + ACCEPTED = "ACCEPTED" + SCHEDULED = "SCHEDULED" + RUNNING = "RUNNING" + FINISHED = "FINISHED" + CANCELED = "CANCELED" + FAILED = "FAILED" + + @classmethod + def list(cls) -> List[str]: + """ + List of available query states as strings + """ + return list(map(lambda c: c.value, cls)) # type: ignore + + +class Tag(BaseModel): + """ + Node tags + """ + + name: str + display_name: str + tag_type: str + + +class AvailabilityState(BaseModel): + """ + Represents the availability state for a node. + """ + + min_temporal_partition: Optional[List[str]] = None + max_temporal_partition: Optional[List[str]] = None + + catalog: str + schema_: Optional[str] + table: str + valid_through_ts: int + + +class Column(BaseModel): + """ + Represents a column + """ + + name: str + type: str + attributes: Optional[List] + + +END_JOB_STATES = [QueryState.FINISHED, QueryState.CANCELED, QueryState.FAILED] diff --git a/datajunction-clients/python/datajunction/nodes.py b/datajunction-clients/python/datajunction/nodes.py new file mode 100644 index 000000000..e13d34874 --- /dev/null +++ b/datajunction-clients/python/datajunction/nodes.py @@ -0,0 +1,344 @@ +"""DataJunction base client setup.""" +import abc + +# pylint: disable=redefined-outer-name, import-outside-toplevel, too-many-lines, protected-access +from typing import Any, Dict, List, Optional, Union + +from pydantic import validator + +from datajunction import models +from datajunction._internal import ClientEntity +from datajunction.exceptions import DJClientException + + +class Namespace(ClientEntity): # pylint: disable=protected-access + """ + Represents a namespace + """ + + namespace: str + + # + # List + # + def nodes(self): + """ + Retrieves all nodes under this namespace. + """ + return self.dj_client._get_nodes_in_namespace( + self.namespace, + ) + + def sources(self): + """ + Retrieves source nodes under this namespace. + """ + return self.dj_client._get_nodes_in_namespace( + self.namespace, + type_=models.NodeType.SOURCE, + ) + + def transforms(self): + """ + Retrieves transform nodes under this namespace. + """ + return self.dj_client._get_nodes_in_namespace( + self.namespace, + type_=models.NodeType.TRANSFORM, + ) + + def cubes(self): + """ + Retrieves cubes under this namespace. + """ + return self.dj_client._get_nodes_in_namespace( + self.namespace, + type_=models.NodeType.CUBE, + ) + + +class Node(ClientEntity): # pylint: disable=protected-access + """ + Represents a DJ node object + """ + + name: str + description: Optional[str] + type: str + mode: Optional[models.NodeMode] + status: Optional[str] = None + display_name: Optional[str] + availability: Optional[models.AvailabilityState] + tags: Optional[List[models.Tag]] + primary_key: Optional[List[str]] + materializations: Optional[List[Dict[str, Any]]] + version: Optional[str] + deactivated_at: Optional[int] + + # + # Node level actions + # + def list_revisions(self) -> List[dict]: + """ + List all revisions of this node + """ + return self.dj_client._get_node_revisions(self.name) + + @abc.abstractmethod + def _update(self) -> "Node": + """ + Update the node for fields that have changed. + """ + + def save(self, mode: Optional[models.NodeMode] = models.NodeMode.PUBLISHED) -> dict: + """ + Saves the node to DJ, whether it existed before or not. + """ + existing_node = self.dj_client._get_node(node_name=self.name) + if "name" in existing_node: + # update + response = self._update() + if not response.ok: # pragma: no cover + raise DJClientException( + f"Error updating node `{self.name}`: {response.text}", + ) + self.refresh() + else: + # create + response = self.dj_client._create_node( + node=self, + mode=mode, + ) # pragma: no cover + if not response.ok: # pragma: no cover + raise DJClientException( + f"Error creating new node `{self.name}`: {response.text}", + ) + return response.json() + + def refresh(self): + """ + Refreshes a node with its latest version from the database. + """ + refreshed_node = self.dj_client._get_node(self.name) + for key, value in refreshed_node.items(): + if hasattr(self, key): + setattr(self, key, value) + return self + + def delete(self): + """ + Deletes the node (softly). We still keep it in an inactive state. + """ + return self.dj_client.delete_node(self.name) + + def restore(self): + """ + Restores (aka reactivates) the node. + """ + return self.dj_client.restore_node(self.name) + + # + # Node attributes level actions + # + def link_dimension( + self, + column: str, + dimension: str, + dimension_column: Optional[str], + ): + """ + Links the dimension to this node via the node's `column` and the dimension's + `dimension_column`. If no `dimension_column` is provided, the dimension's + primary key will be used automatically. + """ + link_response = self.dj_client._link_dimension_to_node( + self.name, + column, + dimension, + dimension_column, + ) + self.refresh() + return link_response + + def unlink_dimension( + self, + column: str, + dimension: str, + dimension_column: Optional[str], + ): + """ + Removes the dimension link on the node's `column` to the dimension. + """ + link_response = self.dj_client._unlink_dimension_from_node( # pylint: disable=protected-access + self.name, + column, + dimension, + dimension_column, + ) + self.refresh() + return link_response + + def add_materialization(self, config: models.MaterializationConfig): + """ + Adds a materialization for the node. This will not work for source nodes + as they don't need to be materialized. + """ + upsert_response = self.dj_client._upsert_materialization( + self.name, + config, + ) + self.refresh() + return upsert_response + + def deactivate_materialization(self, materialization_name: str): + """ + Deactivate a materialization for the node. + """ + response = self.dj_client._deactivate_materialization( + self.name, + materialization_name, + ) + self.refresh() + return response + + def add_availability(self, availability: models.AvailabilityState): + """ + Adds an availability state to the node + """ + return self.dj_client._add_availability_state(self.name, availability) + + def set_column_attributes(self, attributes: List[models.ColumnAttribute]): + """ + Sets attributes for columns on the node + """ + return self.dj_client._set_column_attributes(self.name, attributes) + + +class Source(Node): + """ + DJ source node + """ + + type: str = "source" + catalog: str + schema_: str + table: str + columns: Optional[List[models.Column]] + + @validator("catalog", pre=True) + def parse_cls( # pylint: disable=no-self-argument + cls, + value: Union[str, Dict[str, Any]], + ) -> str: + """ + When `catalog` is a dictionary, parse out the catalog's + name, otherwise just return the string. + """ + if isinstance(value, str): + return value + return value["name"] + + def _update(self) -> "Node": + """ + Update the node for fields that have changed + """ + update_node = models.UpdateNode( + display_name=self.display_name, + description=self.description, + mode=self.mode, + primary_key=self.primary_key, + catalog=self.catalog, + schema_=self.schema_, + table=self.table, + columns=self.columns, + ) + return self.dj_client._update_node(self.name, update_node) + + +class NodeWithQuery(Node): + """ + Nodes with query attribute + """ + + query: str + + def _update(self) -> "Node": + """ + Update the node for fields that have changed. + """ + update_node = models.UpdateNode( + display_name=self.display_name, + description=self.description, + mode=self.mode, + primary_key=self.primary_key, + query=self.query, + ) + return self.dj_client._update_node(self.name, update_node) + + def _validate(self) -> str: + """ + Check if the node is valid by calling the /validate endpoint. + """ + validation = self.dj_client._validate_node(self) + return validation["status"] + + def publish(self) -> bool: + """ + Change a node's mode to published + """ + self.dj_client._publish_node( + self.name, + models.UpdateNode(mode=models.NodeMode.PUBLISHED), + ) + return True + + +class Transform(NodeWithQuery): + """ + DJ transform node + """ + + type: str = "transform" + columns: Optional[List[models.Column]] + + +class Metric(NodeWithQuery): + """ + DJ metric node + """ + + type: str = "metric" + columns: Optional[List[models.Column]] + + def dimensions(self): + """ + Returns the available dimensions for this metric. + """ + metric = self.dj_client.get_metric(self.name) + return metric["dimensions"] + + +class Dimension(NodeWithQuery): + """ + DJ dimension node + """ + + type: str = "dimension" + query: str + columns: Optional[List[models.Column]] + + +class Cube(Node): # pylint: disable=abstract-method + """ + DJ cube node + """ + + type: str = "cube" + query: Optional[str] = None + metrics: List[str] + dimensions: List[str] + filters: Optional[List[str]] + columns: Optional[List[models.Column]] + + def _update(self): # pragma: no cover + pass diff --git a/datajunction-clients/python/pdm.lock b/datajunction-clients/python/pdm.lock new file mode 100644 index 000000000..9460a599c --- /dev/null +++ b/datajunction-clients/python/pdm.lock @@ -0,0 +1,1966 @@ +# This file is @generated by PDM. +# It is not intended for manual editing. + +[[package]] +name = "about-time" +version = "4.2.1" +requires_python = ">=3.7, <4" +summary = "Easily measure timing and throughput of code blocks, with beautiful human friendly representations." + +[[package]] +name = "accept-types" +version = "0.4.1" +summary = "Determine the best content to send in an HTTP response" + +[[package]] +name = "alembic" +version = "1.11.1" +requires_python = ">=3.7" +summary = "A database migration tool for SQLAlchemy." +dependencies = [ + "Mako", + "SQLAlchemy>=1.3.0", + "importlib-metadata; python_version < \"3.9\"", + "importlib-resources; python_version < \"3.9\"", + "typing-extensions>=4", +] + +[[package]] +name = "alive-progress" +version = "3.1.4" +requires_python = ">=3.7, <4" +summary = "A new kind of Progress Bar, with real-time throughput, ETA, and very cool animations!" +dependencies = [ + "about-time==4.2.1", + "grapheme==0.6.0", +] + +[[package]] +name = "amqp" +version = "5.1.1" +requires_python = ">=3.6" +summary = "Low-level AMQP client for Python (fork of amqplib)." +dependencies = [ + "vine>=5.0.0", +] + +[[package]] +name = "antlr4-python3-runtime" +version = "4.12.0" +summary = "ANTLR 4.12.0 runtime for Python 3" + +[[package]] +name = "anyio" +version = "3.7.0" +requires_python = ">=3.7" +summary = "High level compatibility layer for multiple asynchronous event loop implementations" +dependencies = [ + "exceptiongroup; python_version < \"3.11\"", + "idna>=2.8", + "sniffio>=1.1", +] + +[[package]] +name = "asciidag" +version = "0.2.0" +summary = "Draw DAGs (directed acyclic graphs) as ASCII art, à la git log --graph" + +[[package]] +name = "asgiref" +version = "3.7.2" +requires_python = ">=3.7" +summary = "ASGI specs, helper code, and adapters" +dependencies = [ + "typing-extensions>=4; python_version < \"3.11\"", +] + +[[package]] +name = "astroid" +version = "2.15.5" +requires_python = ">=3.7.2" +summary = "An abstract syntax tree for Python with inference support." +dependencies = [ + "lazy-object-proxy>=1.4.0", + "typing-extensions>=4.0.0; python_version < \"3.11\"", + "wrapt<2,>=1.11; python_version < \"3.11\"", + "wrapt<2,>=1.14; python_version >= \"3.11\"", +] + +[[package]] +name = "async-timeout" +version = "4.0.2" +requires_python = ">=3.6" +summary = "Timeout context manager for asyncio programs" + +[[package]] +name = "backports-zoneinfo" +version = "0.2.1" +requires_python = ">=3.6" +summary = "Backport of the standard library zoneinfo module" + +[[package]] +name = "backports-zoneinfo" +version = "0.2.1" +extras = ["tzdata"] +requires_python = ">=3.6" +summary = "Backport of the standard library zoneinfo module" +dependencies = [ + "backports-zoneinfo==0.2.1", + "tzdata", +] + +[[package]] +name = "billiard" +version = "4.1.0" +requires_python = ">=3.7" +summary = "Python multiprocessing fork with improvements and bugfixes" + +[[package]] +name = "cachelib" +version = "0.10.2" +requires_python = ">=3.7" +summary = "A collection of cache libraries in the same API interface." + +[[package]] +name = "celery" +version = "5.3.1" +requires_python = ">=3.8" +summary = "Distributed Task Queue." +dependencies = [ + "backports-zoneinfo>=0.2.1; python_version < \"3.9\"", + "billiard<5.0,>=4.1.0", + "click-didyoumean>=0.3.0", + "click-plugins>=1.1.1", + "click-repl>=0.2.0", + "click<9.0,>=8.1.2", + "kombu<6.0,>=5.3.1", + "python-dateutil>=2.8.2", + "tzdata>=2022.7", + "vine<6.0,>=5.0.0", +] + +[[package]] +name = "certifi" +version = "2023.5.7" +requires_python = ">=3.6" +summary = "Python package for providing Mozilla's CA Bundle." + +[[package]] +name = "cfgv" +version = "3.3.1" +requires_python = ">=3.6.1" +summary = "Validate configuration and produce human readable error messages." + +[[package]] +name = "charset-normalizer" +version = "3.1.0" +requires_python = ">=3.7.0" +summary = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." + +[[package]] +name = "click" +version = "8.1.3" +requires_python = ">=3.7" +summary = "Composable command line interface toolkit" +dependencies = [ + "colorama; platform_system == \"Windows\"", +] + +[[package]] +name = "click-didyoumean" +version = "0.3.0" +requires_python = ">=3.6.2,<4.0.0" +summary = "Enables git-like *did-you-mean* feature in click" +dependencies = [ + "click>=7", +] + +[[package]] +name = "click-plugins" +version = "1.1.1" +summary = "An extension module for click to enable registering CLI commands via setuptools entry-points." +dependencies = [ + "click>=4.0", +] + +[[package]] +name = "click-repl" +version = "0.3.0" +requires_python = ">=3.6" +summary = "REPL plugin for Click" +dependencies = [ + "click>=7.0", + "prompt-toolkit>=3.0.36", +] + +[[package]] +name = "colorama" +version = "0.4.6" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +summary = "Cross-platform colored terminal text." + +[[package]] +name = "coverage" +version = "7.2.7" +requires_python = ">=3.7" +summary = "Code coverage measurement for Python" + +[[package]] +name = "coverage" +version = "7.2.7" +extras = ["toml"] +requires_python = ">=3.7" +summary = "Code coverage measurement for Python" +dependencies = [ + "coverage==7.2.7", + "tomli; python_full_version <= \"3.11.0a6\"", +] + +[[package]] +name = "datajunction-server" +version = "0.0.1a15" +requires_python = "<4.0,>=3.8" +path = "../../datajunction-server" +summary = "DataJunction server library for running to a DataJunction server" +dependencies = [ + "accept-types<1.0.0,>=0.4.1", + "alembic>=1.10.3", + "antlr4-python3-runtime==4.12.0", + "asciidag<1.0.0,>=0.2.0", + "cachelib<1.0.0,>=0.10.2", + "celery<6.0.0,>=5.2.7", + "fastapi<0.80.0,>=0.79.0", + "msgpack<2.0.0,>=1.0.5", + "opentelemetry-instrumentation-fastapi==0.38b0", + "python-dotenv<1.0.0,>=0.19.0", + "pyyaml<7.0,>=6.0", + "redis<5.0.0,>=4.5.4", + "requests<=2.29.0,>=2.28.2", + "rich<14.0.0,>=13.3.3", + "sqlalchemy-utils<1.0.0,>=0.40.0", + "sqlalchemy<2.0.0,>=1.4.41", + "sqlmodel<1.0.0,>=0.0.8", + "sqlparse<1.0.0,>=0.4.3", + "yarl<2.0.0,>=1.8.2", +] + +[[package]] +name = "deprecated" +version = "1.2.14" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +summary = "Python @deprecated decorator to deprecate old python classes, functions or methods." +dependencies = [ + "wrapt<2,>=1.10", +] + +[[package]] +name = "dill" +version = "0.3.6" +requires_python = ">=3.7" +summary = "serialize all of python" + +[[package]] +name = "distlib" +version = "0.3.6" +summary = "Distribution utilities" + +[[package]] +name = "exceptiongroup" +version = "1.1.2" +requires_python = ">=3.7" +summary = "Backport of PEP 654 (exception groups)" + +[[package]] +name = "fastapi" +version = "0.79.1" +requires_python = ">=3.6.1" +summary = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +dependencies = [ + "pydantic!=1.7,!=1.7.1,!=1.7.2,!=1.7.3,!=1.8,!=1.8.1,<2.0.0,>=1.6.2", + "starlette==0.19.1", +] + +[[package]] +name = "filelock" +version = "3.12.2" +requires_python = ">=3.7" +summary = "A platform independent file lock." + +[[package]] +name = "grapheme" +version = "0.6.0" +summary = "Unicode grapheme helpers" + +[[package]] +name = "greenlet" +version = "2.0.2" +requires_python = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" +summary = "Lightweight in-process concurrent programming" + +[[package]] +name = "identify" +version = "2.5.24" +requires_python = ">=3.7" +summary = "File identification library for Python" + +[[package]] +name = "idna" +version = "3.4" +requires_python = ">=3.5" +summary = "Internationalized Domain Names in Applications (IDNA)" + +[[package]] +name = "importlib-metadata" +version = "6.0.1" +requires_python = ">=3.7" +summary = "Read metadata from Python packages" +dependencies = [ + "zipp>=0.5", +] + +[[package]] +name = "importlib-resources" +version = "5.12.0" +requires_python = ">=3.7" +summary = "Read resources from Python packages" +dependencies = [ + "zipp>=3.1.0; python_version < \"3.10\"", +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +requires_python = ">=3.7" +summary = "brain-dead simple config-ini parsing" + +[[package]] +name = "isort" +version = "5.12.0" +requires_python = ">=3.8.0" +summary = "A Python utility / library to sort Python imports." + +[[package]] +name = "kombu" +version = "5.3.1" +requires_python = ">=3.8" +summary = "Messaging library for Python." +dependencies = [ + "amqp<6.0.0,>=5.1.1", + "backports-zoneinfo[tzdata]>=0.2.1; python_version < \"3.9\"", + "typing-extensions; python_version < \"3.10\"", + "vine", +] + +[[package]] +name = "lazy-object-proxy" +version = "1.9.0" +requires_python = ">=3.7" +summary = "A fast and thorough lazy object proxy." + +[[package]] +name = "mako" +version = "1.2.4" +requires_python = ">=3.7" +summary = "A super-fast templating language that borrows the best ideas from the existing templating languages." +dependencies = [ + "MarkupSafe>=0.9.2", +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +requires_python = ">=3.8" +summary = "Python port of markdown-it. Markdown parsing, done right!" +dependencies = [ + "mdurl~=0.1", +] + +[[package]] +name = "markupsafe" +version = "2.1.3" +requires_python = ">=3.7" +summary = "Safely add untrusted strings to HTML/XML markup." + +[[package]] +name = "mccabe" +version = "0.7.0" +requires_python = ">=3.6" +summary = "McCabe checker, plugin for flake8" + +[[package]] +name = "mdurl" +version = "0.1.2" +requires_python = ">=3.7" +summary = "Markdown URL utilities" + +[[package]] +name = "msgpack" +version = "1.0.5" +summary = "MessagePack serializer" + +[[package]] +name = "multidict" +version = "6.0.4" +requires_python = ">=3.7" +summary = "multidict implementation" + +[[package]] +name = "namesgenerator" +version = "0.3" +summary = "" + +[[package]] +name = "nodeenv" +version = "1.8.0" +requires_python = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +summary = "Node.js virtual environment builder" +dependencies = [ + "setuptools", +] + +[[package]] +name = "numpy" +version = "1.24.4" +requires_python = ">=3.8" +summary = "Fundamental package for array computing in Python" + +[[package]] +name = "opentelemetry-api" +version = "1.18.0" +requires_python = ">=3.7" +summary = "OpenTelemetry Python API" +dependencies = [ + "deprecated>=1.2.6", + "importlib-metadata~=6.0.0", + "setuptools>=16.0", +] + +[[package]] +name = "opentelemetry-instrumentation" +version = "0.38b0" +requires_python = ">=3.7" +summary = "Instrumentation Tools & Auto Instrumentation for OpenTelemetry Python" +dependencies = [ + "opentelemetry-api~=1.4", + "setuptools>=16.0", + "wrapt<2.0.0,>=1.0.0", +] + +[[package]] +name = "opentelemetry-instrumentation-asgi" +version = "0.38b0" +requires_python = ">=3.7" +summary = "ASGI instrumentation for OpenTelemetry" +dependencies = [ + "asgiref~=3.0", + "opentelemetry-api~=1.12", + "opentelemetry-instrumentation==0.38b0", + "opentelemetry-semantic-conventions==0.38b0", + "opentelemetry-util-http==0.38b0", +] + +[[package]] +name = "opentelemetry-instrumentation-fastapi" +version = "0.38b0" +requires_python = ">=3.7" +summary = "OpenTelemetry FastAPI Instrumentation" +dependencies = [ + "opentelemetry-api~=1.12", + "opentelemetry-instrumentation-asgi==0.38b0", + "opentelemetry-instrumentation==0.38b0", + "opentelemetry-semantic-conventions==0.38b0", + "opentelemetry-util-http==0.38b0", +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.38b0" +requires_python = ">=3.7" +summary = "OpenTelemetry Semantic Conventions" + +[[package]] +name = "opentelemetry-util-http" +version = "0.38b0" +requires_python = ">=3.7" +summary = "Web util for OpenTelemetry" + +[[package]] +name = "packaging" +version = "23.1" +requires_python = ">=3.7" +summary = "Core utilities for Python packages" + +[[package]] +name = "pandas" +version = "2.0.3" +requires_python = ">=3.8" +summary = "Powerful data structures for data analysis, time series, and statistics" +dependencies = [ + "numpy>=1.20.3; python_version < \"3.10\"", + "numpy>=1.21.0; python_version >= \"3.10\"", + "numpy>=1.23.2; python_version >= \"3.11\"", + "python-dateutil>=2.8.2", + "pytz>=2020.1", + "tzdata>=2022.1", +] + +[[package]] +name = "platformdirs" +version = "3.8.0" +requires_python = ">=3.7" +summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." + +[[package]] +name = "pluggy" +version = "1.2.0" +requires_python = ">=3.7" +summary = "plugin and hook calling mechanisms for python" + +[[package]] +name = "pre-commit" +version = "3.3.3" +requires_python = ">=3.8" +summary = "A framework for managing and maintaining multi-language pre-commit hooks." +dependencies = [ + "cfgv>=2.0.0", + "identify>=1.0.0", + "nodeenv>=0.11.1", + "pyyaml>=5.1", + "virtualenv>=20.10.0", +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.39" +requires_python = ">=3.7.0" +summary = "Library for building powerful interactive command lines in Python" +dependencies = [ + "wcwidth", +] + +[[package]] +name = "pydantic" +version = "1.10.8" +requires_python = ">=3.7" +summary = "Data validation and settings management using python type hints" +dependencies = [ + "typing-extensions>=4.2.0", +] + +[[package]] +name = "pygments" +version = "2.15.1" +requires_python = ">=3.7" +summary = "Pygments is a syntax highlighting package written in Python." + +[[package]] +name = "pylint" +version = "2.17.4" +requires_python = ">=3.7.2" +summary = "python code static checker" +dependencies = [ + "astroid<=2.17.0-dev0,>=2.15.4", + "colorama>=0.4.5; sys_platform == \"win32\"", + "dill>=0.2; python_version < \"3.11\"", + "dill>=0.3.6; python_version >= \"3.11\"", + "isort<6,>=4.2.5", + "mccabe<0.8,>=0.6", + "platformdirs>=2.2.0", + "tomli>=1.1.0; python_version < \"3.11\"", + "tomlkit>=0.10.1", + "typing-extensions>=3.10.0; python_version < \"3.10\"", +] + +[[package]] +name = "pytest" +version = "7.4.0" +requires_python = ">=3.7" +summary = "pytest: simple powerful testing with Python" +dependencies = [ + "colorama; sys_platform == \"win32\"", + "exceptiongroup>=1.0.0rc8; python_version < \"3.11\"", + "iniconfig", + "packaging", + "pluggy<2.0,>=0.12", + "tomli>=1.0.0; python_version < \"3.11\"", +] + +[[package]] +name = "pytest-asyncio" +version = "0.21.0" +requires_python = ">=3.7" +summary = "Pytest support for asyncio" +dependencies = [ + "pytest>=7.0.0", +] + +[[package]] +name = "pytest-cov" +version = "4.1.0" +requires_python = ">=3.7" +summary = "Pytest plugin for measuring coverage." +dependencies = [ + "coverage[toml]>=5.2.1", + "pytest>=4.6", +] + +[[package]] +name = "pytest-integration" +version = "0.2.3" +requires_python = ">=3.6" +summary = "Organizing pytests by integration or not" + +[[package]] +name = "pytest-mock" +version = "3.11.1" +requires_python = ">=3.7" +summary = "Thin-wrapper around the mock package for easier use with pytest" +dependencies = [ + "pytest>=5.0", +] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +summary = "Extensions to the standard Python datetime module" +dependencies = [ + "six>=1.5", +] + +[[package]] +name = "python-dotenv" +version = "0.21.1" +requires_python = ">=3.7" +summary = "Read key-value pairs from a .env file and set them as environment variables" + +[[package]] +name = "pytz" +version = "2023.3" +summary = "World timezone definitions, modern and historical" + +[[package]] +name = "pyyaml" +version = "6.0" +requires_python = ">=3.6" +summary = "YAML parser and emitter for Python" + +[[package]] +name = "redis" +version = "4.6.0" +requires_python = ">=3.7" +summary = "Python client for Redis database and key-value store" +dependencies = [ + "async-timeout>=4.0.2; python_full_version <= \"3.11.2\"", +] + +[[package]] +name = "requests" +version = "2.29.0" +requires_python = ">=3.7" +summary = "Python HTTP for Humans." +dependencies = [ + "certifi>=2017.4.17", + "charset-normalizer<4,>=2", + "idna<4,>=2.5", + "urllib3<1.27,>=1.21.1", +] + +[[package]] +name = "responses" +version = "0.23.1" +requires_python = ">=3.7" +summary = "A utility library for mocking out the `requests` Python library." +dependencies = [ + "pyyaml", + "requests<3.0,>=2.22.0", + "types-PyYAML", + "urllib3>=1.25.10", +] + +[[package]] +name = "rich" +version = "13.4.2" +requires_python = ">=3.7.0" +summary = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +dependencies = [ + "markdown-it-py>=2.2.0", + "pygments<3.0.0,>=2.13.0", + "typing-extensions<5.0,>=4.0.0; python_version < \"3.9\"", +] + +[[package]] +name = "setuptools" +version = "68.0.0" +requires_python = ">=3.7" +summary = "Easily download, build, install, upgrade, and uninstall Python packages" + +[[package]] +name = "six" +version = "1.16.0" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +summary = "Python 2 and 3 compatibility utilities" + +[[package]] +name = "sniffio" +version = "1.3.0" +requires_python = ">=3.7" +summary = "Sniff out which async library your code is running under" + +[[package]] +name = "sqlalchemy" +version = "1.4.41" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +summary = "Database Abstraction Library" +dependencies = [ + "greenlet!=0.4.17; python_version >= \"3\" and (platform_machine == \"aarch64\" or (platform_machine == \"ppc64le\" or (platform_machine == \"x86_64\" or (platform_machine == \"amd64\" or (platform_machine == \"AMD64\" or (platform_machine == \"win32\" or platform_machine == \"WIN32\"))))))", +] + +[[package]] +name = "sqlalchemy-utils" +version = "0.41.1" +requires_python = ">=3.6" +summary = "Various utility functions for SQLAlchemy." +dependencies = [ + "SQLAlchemy>=1.3", +] + +[[package]] +name = "sqlalchemy2-stubs" +version = "0.0.2a34" +requires_python = ">=3.6" +summary = "Typing Stubs for SQLAlchemy 1.4" +dependencies = [ + "typing-extensions>=3.7.4", +] + +[[package]] +name = "sqlmodel" +version = "0.0.8" +requires_python = ">=3.6.1,<4.0.0" +summary = "SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness." +dependencies = [ + "SQLAlchemy<=1.4.41,>=1.4.17", + "pydantic<2.0.0,>=1.8.2", + "sqlalchemy2-stubs", +] + +[[package]] +name = "sqlparse" +version = "0.4.4" +requires_python = ">=3.5" +summary = "A non-validating SQL parser." + +[[package]] +name = "starlette" +version = "0.19.1" +requires_python = ">=3.6" +summary = "The little ASGI library that shines." +dependencies = [ + "anyio<5,>=3.4.0", + "typing-extensions>=3.10.0; python_version < \"3.10\"", +] + +[[package]] +name = "tomli" +version = "2.0.1" +requires_python = ">=3.7" +summary = "A lil' TOML parser" + +[[package]] +name = "tomlkit" +version = "0.11.8" +requires_python = ">=3.7" +summary = "Style preserving TOML library" + +[[package]] +name = "types-pyyaml" +version = "6.0.12.10" +summary = "Typing stubs for PyYAML" + +[[package]] +name = "typing-extensions" +version = "4.7.1" +requires_python = ">=3.7" +summary = "Backported and Experimental Type Hints for Python 3.7+" + +[[package]] +name = "tzdata" +version = "2023.3" +requires_python = ">=2" +summary = "Provider of IANA time zone data" + +[[package]] +name = "urllib3" +version = "1.26.16" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +summary = "HTTP library with thread-safe connection pooling, file post, and more." + +[[package]] +name = "vine" +version = "5.0.0" +requires_python = ">=3.6" +summary = "Promises, promises, promises." + +[[package]] +name = "virtualenv" +version = "20.23.1" +requires_python = ">=3.7" +summary = "Virtual Python Environment builder" +dependencies = [ + "distlib<1,>=0.3.6", + "filelock<4,>=3.12", + "platformdirs<4,>=3.5.1", +] + +[[package]] +name = "wcwidth" +version = "0.2.6" +summary = "Measures the displayed width of unicode strings in a terminal" + +[[package]] +name = "wrapt" +version = "1.15.0" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +summary = "Module for decorators, wrappers and monkey patching." + +[[package]] +name = "yarl" +version = "1.9.2" +requires_python = ">=3.7" +summary = "Yet another URL library" +dependencies = [ + "idna>=2.0", + "multidict>=4.0", +] + +[[package]] +name = "zipp" +version = "3.15.0" +requires_python = ">=3.7" +summary = "Backport of pathlib-compatible object wrapper for zip files" + +[metadata] +lock_version = "4.2" +cross_platform = true +groups = ["default", "pandas", "test"] +content_hash = "sha256:89e92955a0bb0133e3dbceb6d716f5156d93e002679ba528c8537296ca912c99" + +[metadata.files] +"about-time 4.2.1" = [ + {url = "https://files.pythonhosted.org/packages/1c/3f/ccb16bdc53ebb81c1bf837c1ee4b5b0b69584fd2e4a802a2a79936691c0a/about-time-4.2.1.tar.gz", hash = "sha256:6a538862d33ce67d997429d14998310e1dbfda6cb7d9bbfbf799c4709847fece"}, + {url = "https://files.pythonhosted.org/packages/fb/cd/7ee00d6aa023b1d0551da0da5fee3bc23c3eeea632fbfc5126d1fec52b7e/about_time-4.2.1-py3-none-any.whl", hash = "sha256:8bbf4c75fe13cbd3d72f49a03b02c5c7dca32169b6d49117c257e7eb3eaee341"}, +] +"accept-types 0.4.1" = [ + {url = "https://files.pythonhosted.org/packages/08/24/559c0f1959134a0e7615a0ece082734209b1942365ec479cbe3bcca9098b/accept_types-0.4.1-py3-none-any.whl", hash = "sha256:c87feccdffb66b02f9343ff387d7fd5c451ccb2e1221fbd37ea0cedef5cf290f"}, + {url = "https://files.pythonhosted.org/packages/a3/84/6f51d94019411892c9f7fa9d461d4cef06beb35d54cd9944ea19728c4d45/accept-types-0.4.1.tar.gz", hash = "sha256:fb27099716d8f0360408c8ca86d69dbfed44455834b70d1506250abe521b535a"}, +] +"alembic 1.11.1" = [ + {url = "https://files.pythonhosted.org/packages/11/00/46a4f66ad54c661350a1cd5daae4b4ab2232486c55635ee12ff12958b03f/alembic-1.11.1-py3-none-any.whl", hash = "sha256:dc871798a601fab38332e38d6ddb38d5e734f60034baeb8e2db5b642fccd8ab8"}, + {url = "https://files.pythonhosted.org/packages/c6/e3/3d9b95470606b93bd6e6d5c899ed9d0049dfa10246ecca25b18c2c708cdf/alembic-1.11.1.tar.gz", hash = "sha256:6a810a6b012c88b33458fceb869aef09ac75d6ace5291915ba7fae44de372c01"}, +] +"alive-progress 3.1.4" = [ + {url = "https://files.pythonhosted.org/packages/e3/02/5d7f9158d69b36fbe9eb0df8fb435008ec881e41bc7d839239004207d807/alive_progress-3.1.4-py3-none-any.whl", hash = "sha256:c80ad87ce9c1054b01135a87fae69ecebbfc2107497ae87cbe6aec7e534903db"}, + {url = "https://files.pythonhosted.org/packages/ec/55/ae92dce431293998db7575fd2d8315d239d760772df39871b901d3f78357/alive-progress-3.1.4.tar.gz", hash = "sha256:74a95d8d0d42bc99d3a3725dbd06ebb852245f1b64e301a7c375b92b22663f7b"}, +] +"amqp 5.1.1" = [ + {url = "https://files.pythonhosted.org/packages/cb/e7/bcaab89065e17915a28247fa5d4f582ca107b4544e2b1aba92d32f794a0f/amqp-5.1.1.tar.gz", hash = "sha256:2c1b13fecc0893e946c65cbd5f36427861cffa4ea2201d8f6fca22e2a373b5e2"}, + {url = "https://files.pythonhosted.org/packages/de/a3/e7b3b9d34239bae066df135060e225929d639731050c920fdc740d6b7897/amqp-5.1.1-py3-none-any.whl", hash = "sha256:6f0956d2c23d8fa6e7691934d8c3930eadb44972cbbd1a7ae3a520f735d43359"}, +] +"antlr4-python3-runtime 4.12.0" = [ + {url = "https://files.pythonhosted.org/packages/2c/be/14afc05b4789239a9d597a7fdb64abe5d7babb3a28563dc96d9c53c880f7/antlr4_python3_runtime-4.12.0-py3-none-any.whl", hash = "sha256:2c08f4dfbdc7dfd10f680681a96a55579c1e4f866f01d27099c9a54027923d70"}, + {url = "https://files.pythonhosted.org/packages/f5/4a/9335ed671e57bc8cc0a6ef7f17652b978f7098db56e29b9bea5eb7bf4a19/antlr4-python3-runtime-4.12.0.tar.gz", hash = "sha256:0a8b82f55032734f43ed6b60b8a48c25754721a75cd714eb1fe9ce6ed418b361"}, +] +"anyio 3.7.0" = [ + {url = "https://files.pythonhosted.org/packages/68/fe/7ce1926952c8a403b35029e194555558514b365ad77d75125f521a2bec62/anyio-3.7.0-py3-none-any.whl", hash = "sha256:eddca883c4175f14df8aedce21054bfca3adb70ffe76a9f607aef9d7fa2ea7f0"}, + {url = "https://files.pythonhosted.org/packages/c6/b3/fefbf7e78ab3b805dec67d698dc18dd505af7a18a8dd08868c9b4fa736b5/anyio-3.7.0.tar.gz", hash = "sha256:275d9973793619a5374e1c89a4f4ad3f4b0a5510a2b5b939444bee8f4c4d37ce"}, +] +"asciidag 0.2.0" = [ + {url = "https://files.pythonhosted.org/packages/10/9b/00885406d04e6bcad4c51a5bff2a92b6d73ef322d8b30916fe3d28bb2c48/asciidag-0.2.0-py2.py3-none-any.whl", hash = "sha256:f7ea1e6a867ab4c3a2537ff03bc0f25d8fccc2d5109f9f329220ba4fbb1b3e02"}, + {url = "https://files.pythonhosted.org/packages/87/7a/2241c6cc1cd1c34b10ba8ced7cd4b9b47c6609fcb186f4bf2826a2f0b43c/asciidag-0.2.0.tar.gz", hash = "sha256:acf4df123fc222322467d9bdb2020e44b4e1af37d38129092a080c3cda54a788"}, +] +"asgiref 3.7.2" = [ + {url = "https://files.pythonhosted.org/packages/12/19/64e38c1c2cbf0da9635b7082bbdf0e89052e93329279f59759c24a10cc96/asgiref-3.7.2.tar.gz", hash = "sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed"}, + {url = "https://files.pythonhosted.org/packages/9b/80/b9051a4a07ad231558fcd8ffc89232711b4e618c15cb7a392a17384bbeef/asgiref-3.7.2-py3-none-any.whl", hash = "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e"}, +] +"astroid 2.15.5" = [ + {url = "https://files.pythonhosted.org/packages/6f/51/868921f570a1ad2ddefd04594e1f95aacd6208c85f6b0ab75401acf65cfb/astroid-2.15.5-py3-none-any.whl", hash = "sha256:078e5212f9885fa85fbb0cf0101978a336190aadea6e13305409d099f71b2324"}, + {url = "https://files.pythonhosted.org/packages/e9/8d/338debdfc65383c2e1a0eaf336810ced0e8bc58a3f64d8f957cfb7b19e84/astroid-2.15.5.tar.gz", hash = "sha256:1039262575027b441137ab4a62a793a9b43defb42c32d5670f38686207cd780f"}, +] +"async-timeout 4.0.2" = [ + {url = "https://files.pythonhosted.org/packages/54/6e/9678f7b2993537452710ffb1750c62d2c26df438aa621ad5fa9d1507a43a/async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, + {url = "https://files.pythonhosted.org/packages/d6/c1/8991e7c5385b897b8c020cdaad718c5b087a6626d1d11a23e1ea87e325a7/async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, +] +"backports-zoneinfo 0.2.1" = [ + {url = "https://files.pythonhosted.org/packages/1a/ab/3e941e3fcf1b7d3ab3d0233194d99d6a0ed6b24f8f956fc81e47edc8c079/backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7b0a64cda4145548fed9efc10322770f929b944ce5cee6c0dfe0c87bf4c0c8c9"}, + {url = "https://files.pythonhosted.org/packages/1c/96/baaca3ad1b06d97138d42a225e4d4d27cd1586b646740f771706cd2d812c/backports.zoneinfo-0.2.1-cp37-cp37m-win32.whl", hash = "sha256:e55b384612d93be96506932a786bbcde5a2db7a9e6a4bb4bffe8b733f5b9036b"}, + {url = "https://files.pythonhosted.org/packages/28/d5/e2f3d6a52870045afd8c37b2681c47fd0b98679cd4851e349bfd7e19cfd7/backports.zoneinfo-0.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:8439c030a11780786a2002261569bdf362264f605dfa4d65090b64b05c9f79a7"}, + {url = "https://files.pythonhosted.org/packages/33/1c/9357061860f5d3a09e1877aa4cf7c004c55eec40a1036761144ef24d8a1d/backports.zoneinfo-0.2.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:da6013fd84a690242c310d77ddb8441a559e9cb3d3d59ebac9aca1a57b2e18bc"}, + {url = "https://files.pythonhosted.org/packages/4a/6d/eca004eeadcbf8bd64cc96feb9e355536147f0577420b44d80c7cac70767/backports.zoneinfo-0.2.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:8961c0f32cd0336fb8e8ead11a1f8cd99ec07145ec2931122faaac1c8f7fd987"}, + {url = "https://files.pythonhosted.org/packages/4c/7e/ed8af95bed90eeccfb4a4fe6ec424bc7a79e1aa983e54dd1d9062d9fa20b/backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5c144945a7752ca544b4b78c8c41544cdfaf9786f25fe5ffb10e838e19a27570"}, + {url = "https://files.pythonhosted.org/packages/6c/99/513f2c4dd41522eefc42feb86854f6cf3b1add9c175c14d90c070775e484/backports.zoneinfo-0.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a76b38c52400b762e48131494ba26be363491ac4f9a04c1b7e92483d169f6582"}, + {url = "https://files.pythonhosted.org/packages/74/a1/323f86a5ca5a559d452affb879512365a0473529398bfcf2d712a40ae088/backports.zoneinfo-0.2.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac"}, + {url = "https://files.pythonhosted.org/packages/78/cc/e27fd6493bbce8dbea7e6c1bc861fe3d3bc22c4f7c81f4c3befb8ff5bfaf/backports.zoneinfo-0.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6"}, + {url = "https://files.pythonhosted.org/packages/ad/85/475e514c3140937cf435954f78dedea1861aeab7662d11de232bdaa90655/backports.zoneinfo-0.2.1.tar.gz", hash = "sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2"}, + {url = "https://files.pythonhosted.org/packages/c0/34/5fdb0a3a28841d215c255be8fc60b8666257bb6632193c86fd04b63d4a31/backports.zoneinfo-0.2.1-cp38-cp38-win32.whl", hash = "sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328"}, + {url = "https://files.pythonhosted.org/packages/c1/8f/9b1b920a6a95652463143943fa3b8c000cb0b932ab463764a6f2a2416560/backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e81b76cace8eda1fca50e345242ba977f9be6ae3945af8d46326d776b4cf78d1"}, + {url = "https://files.pythonhosted.org/packages/d1/04/8f2fed9c0cb9c88442fc8d6372cb0f5738fb05a65b45e2d371fbc8a15087/backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:17746bd546106fa389c51dbea67c8b7c8f0d14b5526a579ca6ccf5ed72c526cf"}, + {url = "https://files.pythonhosted.org/packages/d4/79/249bd3c4f794741f04f1e0ff33ad3cca9b2d1f4299b73f78d0d9bc9ec8dc/backports.zoneinfo-0.2.1-cp36-cp36m-win32.whl", hash = "sha256:e8236383a20872c0cdf5a62b554b27538db7fa1bbec52429d8d106effbaeca08"}, + {url = "https://files.pythonhosted.org/packages/ef/9a/8de8f379d5b3961a517762cc051b366de3f7d4d3a2250120e7a71e25fab4/backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:89a48c0d158a3cc3f654da4c2de1ceba85263fafb861b98b59040a5086259722"}, + {url = "https://files.pythonhosted.org/packages/f9/04/33e910faffe91a5680d68a064162525779259ae5de3b0c0c5bd9c4e900e0/backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:1c5742112073a563c81f786e77514969acb58649bcdf6cdf0b4ed31a348d4546"}, +] +"billiard 4.1.0" = [ + {url = "https://files.pythonhosted.org/packages/b3/9c/c02d3988ddb0e32e974db1d8434616f1503b12fc7087bf18243ccd69a60a/billiard-4.1.0.tar.gz", hash = "sha256:1ad2eeae8e28053d729ba3373d34d9d6e210f6e4d8bf0a9c64f92bd053f1edf5"}, + {url = "https://files.pythonhosted.org/packages/e3/a4/ae83eeb0563804a4148ff0e52f530e675bc90718c7c770cdfe4af1340c86/billiard-4.1.0-py3-none-any.whl", hash = "sha256:0f50d6be051c6b2b75bfbc8bfd85af195c5739c281d3f5b86a5640c65563614a"}, +] +"cachelib 0.10.2" = [ + {url = "https://files.pythonhosted.org/packages/70/0b/e7647e072ff60997d69517072145ef56898278afda7deff7cc6858b1541f/cachelib-0.10.2.tar.gz", hash = "sha256:593faeee62a7c037d50fc835617a01b887503f972fb52b188ae7e50e9cb69740"}, + {url = "https://files.pythonhosted.org/packages/8c/83/449fb201dd5a6536bb022eb50ddc58da9e3d5cdf674c12df2f6dd46bd52f/cachelib-0.10.2-py3-none-any.whl", hash = "sha256:42d49f2fad9310dd946d7be73d46776bcd4d5fde4f49ad210cfdd447fbdfc346"}, +] +"celery 5.3.1" = [ + {url = "https://files.pythonhosted.org/packages/18/b9/cb8d519ea0094b9b8fe7480225c14937517729f8ec927643dc7379904f64/celery-5.3.1-py3-none-any.whl", hash = "sha256:27f8f3f3b58de6e0ab4f174791383bbd7445aff0471a43e99cfd77727940753f"}, + {url = "https://files.pythonhosted.org/packages/a4/e2/102f8d3453a9f1c6918245a97b9b8e7352a2925d4c5477a7401de2bb54dc/celery-5.3.1.tar.gz", hash = "sha256:f84d1c21a1520c116c2b7d26593926581191435a03aa74b77c941b93ca1c6210"}, +] +"certifi 2023.5.7" = [ + {url = "https://files.pythonhosted.org/packages/93/71/752f7a4dd4c20d6b12341ed1732368546bc0ca9866139fe812f6009d9ac7/certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"}, + {url = "https://files.pythonhosted.org/packages/9d/19/59961b522e6757f0c9097e4493fa906031b95b3ebe9360b2c3083561a6b4/certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"}, +] +"cfgv 3.3.1" = [ + {url = "https://files.pythonhosted.org/packages/6d/82/0a0ebd35bae9981dea55c06f8e6aaf44a49171ad798795c72c6f64cba4c2/cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, + {url = "https://files.pythonhosted.org/packages/c4/bf/d0d622b660d414a47dc7f0d303791a627663f554345b21250e39e7acb48b/cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, +] +"charset-normalizer 3.1.0" = [ + {url = "https://files.pythonhosted.org/packages/00/47/f14533da238134f5067fb1d951eb03d5c4be895d6afb11c7ebd07d111acb/charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28"}, + {url = "https://files.pythonhosted.org/packages/01/c7/0407de35b70525dba2a58a2724a525cf882ee76c3d2171d834463c5d2881/charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb"}, + {url = "https://files.pythonhosted.org/packages/05/f3/86b5fcb5c8fe8b4231362918a7c4d8f549c56561c5fdb495a3c5b41c6862/charset_normalizer-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8"}, + {url = "https://files.pythonhosted.org/packages/07/6b/98d41a0221991a806e88c95bfeecf8935fbf465b02eb4b469770d572183a/charset_normalizer-3.1.0-cp37-cp37m-win32.whl", hash = "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974"}, + {url = "https://files.pythonhosted.org/packages/0a/67/8d3d162ec6641911879651cdef670c3c6136782b711d7f8e82e2fffe06e0/charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017"}, + {url = "https://files.pythonhosted.org/packages/12/12/c5c39f5a149cd6788d2e40cea5618bae37380e2754fcdf53dc9e01bdd33a/charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41"}, + {url = "https://files.pythonhosted.org/packages/12/68/4812f9b05ac0a2b7619ac3dd7d7e3fc52c12006b84617021c615fc2fcf42/charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e"}, + {url = "https://files.pythonhosted.org/packages/13/b7/21729a6d512246aa0bb872b90aea0d9fcd1b293762cdb1d1d33c01140074/charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326"}, + {url = "https://files.pythonhosted.org/packages/16/58/19fd2f62e6ff44ba0db0cd44b584790555e2cde09293149f4409d654811b/charset_normalizer-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6"}, + {url = "https://files.pythonhosted.org/packages/18/36/7ae10a3dd7f9117b61180671f8d1e4802080cca88ad40aaabd3dad8bab0e/charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62"}, + {url = "https://files.pythonhosted.org/packages/1c/9b/de2adc43345623da8e7c958719528a42b6d87d2601017ce1187d43b8a2d7/charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230"}, + {url = "https://files.pythonhosted.org/packages/1f/be/c6c76cf8fcf6918922223203c83ba8192eff1c6a709e8cfec7f5ca3e7d2d/charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854"}, + {url = "https://files.pythonhosted.org/packages/21/16/1b0d8fdcb81bbf180976af4f867ce0f2244d303ab10d452fde361dec3b5c/charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be"}, + {url = "https://files.pythonhosted.org/packages/23/13/cf5d7bb5bc95f120df64d6c470581189df51d7f011560b2a06a395b7a120/charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137"}, + {url = "https://files.pythonhosted.org/packages/26/20/83e1804a62b25891c4e770c94d9fd80233bbb3f2a51c4fadee7a196e5a5b/charset_normalizer-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59"}, + {url = "https://files.pythonhosted.org/packages/2c/2f/ec805104098085728b7cb610deede7195c6fa59f51942422f02cc427b6f6/charset_normalizer-3.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649"}, + {url = "https://files.pythonhosted.org/packages/2e/25/3eab2b38fef9ae59f7b4e9c1e62eb50609d911867e5acabace95fe25c0b1/charset_normalizer-3.1.0-cp310-cp310-win32.whl", hash = "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448"}, + {url = "https://files.pythonhosted.org/packages/31/8b/81c3515a69d06b501fcce69506af57a7a19bd9f42cabd1a667b1b40f2c55/charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b"}, + {url = "https://files.pythonhosted.org/packages/33/10/c87ba15f779f8251ae55fa147631339cd91e7af51c3c133d2687c6e41800/charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11"}, + {url = "https://files.pythonhosted.org/packages/33/97/9967fb2d364a9da38557e4af323abcd58cc05bdd8f77e9fd5ae4882772cc/charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706"}, + {url = "https://files.pythonhosted.org/packages/45/3d/fa2683f5604f99fba5098a7313e5d4846baaecbee754faf115907f21a85f/charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f"}, + {url = "https://files.pythonhosted.org/packages/4e/11/f7077d78b18aca8ea3186a706c0221aa2bc34c442a3d3bdf3ad401a29052/charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0"}, + {url = "https://files.pythonhosted.org/packages/4f/18/92866f050f7114ba38aba4f4a69f83cc2a25dc2e5a8af4b44fd1bfd6d528/charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d"}, + {url = "https://files.pythonhosted.org/packages/4f/7c/af43743567a7da2a069b4f9fa31874c3c02b963cd1fb84fe1e7568a567e6/charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7"}, + {url = "https://files.pythonhosted.org/packages/4f/a2/9031ba4a008e11a21d7b7aa41751290d2f2035a2f14ecb6e589771a17c47/charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b"}, + {url = "https://files.pythonhosted.org/packages/56/24/5f2dedcf3d0673931b6200c410832ae44b376848bc899dbf1fa6c91c4ebe/charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324"}, + {url = "https://files.pythonhosted.org/packages/5d/2b/4d8c80400c04ae3c8dbc847de092e282b5c7b17f8f9505d68bb3e5815c71/charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb"}, + {url = "https://files.pythonhosted.org/packages/61/e3/ad9ae58b28482d1069eba1edec2be87701f5dd6fd6024a665020d66677a0/charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d"}, + {url = "https://files.pythonhosted.org/packages/67/30/dbab1fe5ab2ce5d3d517ad9936170d896e9687f3860a092519f1fe359812/charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1"}, + {url = "https://files.pythonhosted.org/packages/67/df/660e9665ace7ad711e275194a86cb757fb4d4e513fae5ff3d39573db4984/charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60"}, + {url = "https://files.pythonhosted.org/packages/68/77/af702eba147ba963b27eb00832cef6b8c4cb9fcf7404a476993876434b93/charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd"}, + {url = "https://files.pythonhosted.org/packages/69/22/66351781e668158feef71c5e3b059a79ecc9efc3ef84a45888b0f3a933d5/charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f"}, + {url = "https://files.pythonhosted.org/packages/6d/59/59a3f4d8a59ee270da77f9e954a0e284c9d6884d39ec69d696d9aa5ff2f2/charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0"}, + {url = "https://files.pythonhosted.org/packages/72/90/667a6bc6abe42fc10adf4cd2c1e1c399d78e653dbac4c8018350843d4ab7/charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0"}, + {url = "https://files.pythonhosted.org/packages/74/5f/361202de730532028458b729781b8435f320e31a622c27f30e25eec80513/charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0"}, + {url = "https://files.pythonhosted.org/packages/74/f1/d0b8385b574f7e086fb6709e104b696707bd3742d54a6caf0cebbb7e975b/charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce"}, + {url = "https://files.pythonhosted.org/packages/76/ad/516fed8ffaf02e7a01cd6f6e9d101a6dec64d4db53bec89d30802bf30a96/charset_normalizer-3.1.0-cp38-cp38-win32.whl", hash = "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0"}, + {url = "https://files.pythonhosted.org/packages/82/b9/51b66a647be8685dee75b7807e0f750edf5c1e4f29bc562ad285c501e3c7/charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c"}, + {url = "https://files.pythonhosted.org/packages/84/23/f60cda6c70ae922ad78368982f06e7fef258fba833212f26275fe4727dc4/charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84"}, + {url = "https://files.pythonhosted.org/packages/85/e8/18d408d8fe29a56012c10d6b15960940b83f06620e9d7481581cdc6d9901/charset_normalizer-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df"}, + {url = "https://files.pythonhosted.org/packages/94/70/23981e7bf098efbc4037e7c66d28a10e950d9296c08c6dea8ef290f9c79e/charset_normalizer-3.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e"}, + {url = "https://files.pythonhosted.org/packages/9a/f1/ff81439aa09070fee64173e6ca6ce1342f2b1cca997bcaae89e443812684/charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f"}, + {url = "https://files.pythonhosted.org/packages/9e/62/a1e0a8f8830c92014602c8a88a1a20b8a68d636378077381f671e6e1cec9/charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a"}, + {url = "https://files.pythonhosted.org/packages/a2/6c/5167f08da5298f383036c33cb749ab5b3405fd07853edc8314c6882c01b8/charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e"}, + {url = "https://files.pythonhosted.org/packages/a4/03/355281b62c26712a50c6a9dd75339d8cdd58488fd7bf2556ba1320ebd315/charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d"}, + {url = "https://files.pythonhosted.org/packages/a9/83/138d2624fdbcb62b7e14715eb721d44347e41a1b4c16544661e940793f49/charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f"}, + {url = "https://files.pythonhosted.org/packages/ac/7f/62d5dff4e9cb993e4b0d4ea78a74cc84d7d92120879529e0ce0965765936/charset_normalizer-3.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f"}, + {url = "https://files.pythonhosted.org/packages/ac/c5/990bc41a98b7fa2677c665737fdf278bb74ad4b199c56b6b564b3d4cbfc5/charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c"}, + {url = "https://files.pythonhosted.org/packages/ad/83/994bfca99e29f1bab66b9248e739360ee70b5aae0a5ee488cd776501edbc/charset_normalizer-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755"}, + {url = "https://files.pythonhosted.org/packages/b0/55/d8ef4c8c7d2a8b3a16e7d9b03c59475c2ee96a0e0c90b14c99faaac0ee3b/charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8"}, + {url = "https://files.pythonhosted.org/packages/bb/dc/58fdef3ab85e8e7953a8b89ef1d2c06938b8ad88d9617f22967e1a90e6b8/charset_normalizer-3.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c"}, + {url = "https://files.pythonhosted.org/packages/bc/08/7e7c97399806366ca515a049c3a1e4b644a6a2048bed16e5e67bfaafd0aa/charset_normalizer-3.1.0-cp311-cp311-win32.whl", hash = "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909"}, + {url = "https://files.pythonhosted.org/packages/bc/92/ac692a303e53cdc8852ce72b1ac364b493ca5c9206a5c8db5b30a7f3019c/charset_normalizer-3.1.0-cp39-cp39-win32.whl", hash = "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1"}, + {url = "https://files.pythonhosted.org/packages/c2/35/dfb4032f5712747d3dcfdd19d0768f6d8f60910ae24ed066ecbf442be013/charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203"}, + {url = "https://files.pythonhosted.org/packages/c6/ab/43ea052756b2f2dcb6a131897811c0e2704b0288f090336217d3346cd682/charset_normalizer-3.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1"}, + {url = "https://files.pythonhosted.org/packages/c9/8c/a76dd9f2c8803eb147e1e715727f5c3ba0ef39adaadf66a7b3698c113180/charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5"}, + {url = "https://files.pythonhosted.org/packages/cc/f6/21a66e524658bd1dd7b89ac9d1ee8f7823f2d9701a2fbc458ab9ede53c63/charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795"}, + {url = "https://files.pythonhosted.org/packages/d1/ff/51fe7e6446415f143b159740c727850172bc35622b2a06dde3354bdebaf3/charset_normalizer-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b"}, + {url = "https://files.pythonhosted.org/packages/d5/92/86c0f0e66e897f6818c46dadef328a5b345d061688f9960fc6ca1fd03dbe/charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31"}, + {url = "https://files.pythonhosted.org/packages/d7/4c/37ad75674e8c6bc22ab01bef673d2d6e46ee44203498c9a26aa23959afe5/charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1"}, + {url = "https://files.pythonhosted.org/packages/d8/ca/a7ff600781bf1e5f702ba26bb82f2ba1d3a873a3f8ad73cc44c79dfaefa9/charset_normalizer-3.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14"}, + {url = "https://files.pythonhosted.org/packages/dd/39/6276cf5a395ffd39b77dadf0e2fcbfca8dbfe48c56ada250c40086055143/charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9"}, + {url = "https://files.pythonhosted.org/packages/e1/7c/398600268fc98b7e007f5a716bd60903fff1ecff75e45f5700212df5cd76/charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19"}, + {url = "https://files.pythonhosted.org/packages/e1/b4/53678b2a14e0496fc167fe9b9e726ad33d670cfd2011031aa5caeee6b784/charset_normalizer-3.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373"}, + {url = "https://files.pythonhosted.org/packages/e5/aa/9d2d60d6a566423da96c15cd11cbb88a70f9aff9a4db096094ee19179cab/charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac"}, + {url = "https://files.pythonhosted.org/packages/e6/98/a3f65f57651da1cecaed91d6f75291995d56c97442fa2a43d2a421139adf/charset_normalizer-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23"}, + {url = "https://files.pythonhosted.org/packages/ea/38/d31c7906c4be13060c1a5034087966774ef33ab57ff2eee76d71265173c3/charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531"}, + {url = "https://files.pythonhosted.org/packages/ef/81/14b3b8f01ddaddad6cdec97f2f599aa2fa466bd5ee9af99b08b7713ccd29/charset_normalizer-3.1.0-py3-none-any.whl", hash = "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d"}, + {url = "https://files.pythonhosted.org/packages/f2/b7/e21e16c98575616f4ce09dc766dbccdac0ca119c176b184d46105e971a84/charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"}, + {url = "https://files.pythonhosted.org/packages/f2/d7/6ee92c11eda3f3c9cac1e059901092bfdf07388be7d2e60ac627527eee62/charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1"}, + {url = "https://files.pythonhosted.org/packages/f4/0a/8c03913ed1eca9d831db0c28759edb6ce87af22bb55dbc005a52525a75b6/charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a"}, + {url = "https://files.pythonhosted.org/packages/f6/0f/de1c4030fd669e6719277043e3b0f152a83c118dd1020cf85b51d443d04a/charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e"}, + {url = "https://files.pythonhosted.org/packages/f8/ed/500609cb2457b002242b090c814549997424d72690ef3058cfdfca91f68b/charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b"}, + {url = "https://files.pythonhosted.org/packages/fa/8e/2e5c742c3082bce3eea2ddd5b331d08050cda458bc362d71c48e07a44719/charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6"}, + {url = "https://files.pythonhosted.org/packages/ff/d7/8d757f8bd45be079d76309248845a04f09619a7b17d6dfc8c9ff6433cac2/charset-normalizer-3.1.0.tar.gz", hash = "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5"}, +] +"click 8.1.3" = [ + {url = "https://files.pythonhosted.org/packages/59/87/84326af34517fca8c58418d148f2403df25303e02736832403587318e9e8/click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, + {url = "https://files.pythonhosted.org/packages/c2/f1/df59e28c642d583f7dacffb1e0965d0e00b218e0186d7858ac5233dce840/click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, +] +"click-didyoumean 0.3.0" = [ + {url = "https://files.pythonhosted.org/packages/2f/a7/822fbc659be70dcb75a91fb91fec718b653326697d0e9907f4f90114b34f/click-didyoumean-0.3.0.tar.gz", hash = "sha256:f184f0d851d96b6d29297354ed981b7dd71df7ff500d82fa6d11f0856bee8035"}, + {url = "https://files.pythonhosted.org/packages/ad/36/4599267417fc78b587b1588e0647a468c60b36c02bb723d450d050738fa8/click_didyoumean-0.3.0-py3-none-any.whl", hash = "sha256:a0713dc7a1de3f06bc0df5a9567ad19ead2d3d5689b434768a6145bff77c0667"}, +] +"click-plugins 1.1.1" = [ + {url = "https://files.pythonhosted.org/packages/5f/1d/45434f64ed749540af821fd7e42b8e4d23ac04b1eda7c26613288d6cd8a8/click-plugins-1.1.1.tar.gz", hash = "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b"}, + {url = "https://files.pythonhosted.org/packages/e9/da/824b92d9942f4e472702488857914bdd50f73021efea15b4cad9aca8ecef/click_plugins-1.1.1-py2.py3-none-any.whl", hash = "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8"}, +] +"click-repl 0.3.0" = [ + {url = "https://files.pythonhosted.org/packages/52/40/9d857001228658f0d59e97ebd4c346fe73e138c6de1bce61dc568a57c7f8/click_repl-0.3.0-py3-none-any.whl", hash = "sha256:fb7e06deb8da8de86180a33a9da97ac316751c094c6899382da7feeeeb51b812"}, + {url = "https://files.pythonhosted.org/packages/cb/a2/57f4ac79838cfae6912f997b4d1a64a858fb0c86d7fcaae6f7b58d267fca/click-repl-0.3.0.tar.gz", hash = "sha256:17849c23dba3d667247dc4defe1757fff98694e90fe37474f3feebb69ced26a9"}, +] +"colorama 0.4.6" = [ + {url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] +"coverage 7.2.7" = [ + {url = "https://files.pythonhosted.org/packages/01/24/be01e62a7bce89bcffe04729c540382caa5a06bee45ae42136c93e2499f5/coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8"}, + {url = "https://files.pythonhosted.org/packages/03/ec/6f30b4e0c96ce03b0e64aec46b4af2a8c49b70d1b5d0d69577add757b946/coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a"}, + {url = "https://files.pythonhosted.org/packages/04/d6/8cba3bf346e8b1a4fb3f084df7d8cea25a6b6c56aaca1f2e53829be17e9e/coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d"}, + {url = "https://files.pythonhosted.org/packages/04/fa/43b55101f75a5e9115259e8be70ff9279921cb6b17f04c34a5702ff9b1f7/coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97"}, + {url = "https://files.pythonhosted.org/packages/0d/31/340428c238eb506feb96d4fb5c9ea614db1149517f22cc7ab8c6035ef6d9/coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050"}, + {url = "https://files.pythonhosted.org/packages/0e/bc/7e3a31534fabb043269f14fb64e2bb2733f85d4cf39e5bbc71357c57553a/coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0"}, + {url = "https://files.pythonhosted.org/packages/15/81/b108a60bc758b448c151e5abceed027ed77a9523ecbc6b8a390938301841/coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01"}, + {url = "https://files.pythonhosted.org/packages/1f/e9/d6730247d8dec2a3dddc520ebe11e2e860f0f98cee3639e23de6cf920255/coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850"}, + {url = "https://files.pythonhosted.org/packages/22/c1/2f6c1b6f01a0996c9e067a9c780e1824351dbe17faae54388a4477e6d86f/coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959"}, + {url = "https://files.pythonhosted.org/packages/24/df/6765898d54ea20e3197a26d26bb65b084deefadd77ce7de946b9c96dfdc5/coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4"}, + {url = "https://files.pythonhosted.org/packages/28/d7/9a8de57d87f4bbc6f9a6a5ded1eaac88a89bf71369bb935dac3c0cf2893e/coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5"}, + {url = "https://files.pythonhosted.org/packages/29/8f/4fad1c2ba98104425009efd7eaa19af9a7c797e92d40cd2ec026fa1f58cb/coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495"}, + {url = "https://files.pythonhosted.org/packages/2b/86/3dbf9be43f8bf6a5ca28790a713e18902b2d884bc5fa9512823a81dff601/coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1"}, + {url = "https://files.pythonhosted.org/packages/3d/80/7060a445e1d2c9744b683dc935248613355657809d6c6b2716cdf4ca4766/coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb"}, + {url = "https://files.pythonhosted.org/packages/44/55/49f65ccdd4dfd6d5528e966b28c37caec64170c725af32ab312889d2f857/coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e"}, + {url = "https://files.pythonhosted.org/packages/45/8b/421f30467e69ac0e414214856798d4bc32da1336df745e49e49ae5c1e2a8/coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59"}, + {url = "https://files.pythonhosted.org/packages/4a/fb/78986d3022e5ccf2d4370bc43a5fef8374f092b3c21d32499dee8e30b7b6/coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e"}, + {url = "https://files.pythonhosted.org/packages/61/90/c76b9462f39897ebd8714faf21bc985b65c4e1ea6dff428ea9dc711ed0dd/coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6"}, + {url = "https://files.pythonhosted.org/packages/61/af/5964b8d7d9a5c767785644d9a5a63cacba9a9c45cc42ba06d25895ec87be/coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7"}, + {url = "https://files.pythonhosted.org/packages/66/2e/c99fe1f6396d93551aa352c75410686e726cd4ea104479b9af1af22367ce/coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250"}, + {url = "https://files.pythonhosted.org/packages/67/a2/6fa66a50e6e894286d79a3564f42bd54a9bd27049dc0a63b26d9924f0aa3/coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9"}, + {url = "https://files.pythonhosted.org/packages/67/d7/cd8fe689b5743fffac516597a1222834c42b80686b99f5b44ef43ccc2a43/coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe"}, + {url = "https://files.pythonhosted.org/packages/67/fb/b3b1d7887e1ea25a9608b0776e480e4bbc303ca95a31fd585555ec4fff5a/coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d"}, + {url = "https://files.pythonhosted.org/packages/68/5f/d2bd0f02aa3c3e0311986e625ccf97fdc511b52f4f1a063e4f37b624772f/coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a"}, + {url = "https://files.pythonhosted.org/packages/69/8c/26a95b08059db1cbb01e4b0e6d40f2e9debb628c6ca86b78f625ceaf9bab/coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511"}, + {url = "https://files.pythonhosted.org/packages/6e/ea/4a252dc77ca0605b23d477729d139915e753ee89e4c9507630e12ad64a80/coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de"}, + {url = "https://files.pythonhosted.org/packages/7a/05/084864fa4bbf8106f44fb72a56e67e0cd372d3bf9d893be818338c81af5d/coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb"}, + {url = "https://files.pythonhosted.org/packages/7b/e3/f552d5871943f747165b92a924055c5d6daa164ae659a13f9018e22f3990/coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6"}, + {url = "https://files.pythonhosted.org/packages/80/d7/67937c80b8fd4c909fdac29292bc8b35d9505312cff6bcab41c53c5b1df6/coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f"}, + {url = "https://files.pythonhosted.org/packages/88/8b/b0d9fe727acae907fa7f1c8194ccb6fe9d02e1c3e9001ecf74c741f86110/coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9"}, + {url = "https://files.pythonhosted.org/packages/88/da/495944ebf0ad246235a6bd523810d9f81981f9b81c6059ba1f56e943abe0/coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9"}, + {url = "https://files.pythonhosted.org/packages/8c/95/16eed713202406ca0a37f8ac259bbf144c9d24f9b8097a8e6ead61da2dbb/coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3"}, + {url = "https://files.pythonhosted.org/packages/8d/d6/53e999ec1bf7498ca4bc5f3b8227eb61db39068d2de5dcc359dec5601b5a/coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02"}, + {url = "https://files.pythonhosted.org/packages/8f/a8/12cc7b261f3082cc299ab61f677f7e48d93e35ca5c3c2f7241ed5525ccea/coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833"}, + {url = "https://files.pythonhosted.org/packages/91/e8/469ed808a782b9e8305a08bad8c6fa5f8e73e093bda6546c5aec68275bff/coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353"}, + {url = "https://files.pythonhosted.org/packages/94/4e/d4e46a214ae857be3d7dc5de248ba43765f60daeb1ab077cb6c1536c7fba/coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818"}, + {url = "https://files.pythonhosted.org/packages/9f/5c/d9760ac497c41f9c4841f5972d0edf05d50cad7814e86ee7d133ec4a0ac8/coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d"}, + {url = "https://files.pythonhosted.org/packages/a7/cd/3ce94ad9d407a052dc2a74fbeb1c7947f442155b28264eb467ee78dea812/coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb"}, + {url = "https://files.pythonhosted.org/packages/a9/0c/4a848ae663b47f1195abcb09a951751dd61f80b503303b9b9d768e0fd321/coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27"}, + {url = "https://files.pythonhosted.org/packages/b1/96/c12ed0dfd4ec587f3739f53eb677b9007853fd486ccb0e7d5512a27bab2e/coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562"}, + {url = "https://files.pythonhosted.org/packages/b1/d5/a8e276bc005e42114468d4fe03e0a9555786bc51cbfe0d20827a46c1565a/coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb"}, + {url = "https://files.pythonhosted.org/packages/b4/bd/1b2331e3a04f4cc9b7b332b1dd0f3a1261dfc4114f8479bebfcc2afee9e8/coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063"}, + {url = "https://files.pythonhosted.org/packages/b7/00/14b00a0748e9eda26e97be07a63cc911108844004687321ddcc213be956c/coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3"}, + {url = "https://files.pythonhosted.org/packages/b8/9d/926fce7e03dbfc653104c2d981c0fa71f0572a9ebd344d24c573bd6f7c4f/coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6"}, + {url = "https://files.pythonhosted.org/packages/ba/92/69c0722882643df4257ecc5437b83f4c17ba9e67f15dc6b77bad89b6982e/coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a"}, + {url = "https://files.pythonhosted.org/packages/bb/e9/88747b40c8fb4a783b40222510ce6d66170217eb05d7f46462c36b4fa8cc/coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2"}, + {url = "https://files.pythonhosted.org/packages/c1/49/4d487e2ad5d54ed82ac1101e467e8994c09d6123c91b2a962145f3d262c2/coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f"}, + {url = "https://files.pythonhosted.org/packages/c3/1c/6b3c9c363fb1433c79128e0d692863deb761b1b78162494abb9e5c328bc0/coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c"}, + {url = "https://files.pythonhosted.org/packages/c6/fa/529f55c9a1029c840bcc9109d5a15ff00478b7ff550a1ae361f8745f8ad5/coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f"}, + {url = "https://files.pythonhosted.org/packages/c6/fc/be19131010930a6cf271da48202c8cc1d3f971f68c02fb2d3a78247f43dc/coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5"}, + {url = "https://files.pythonhosted.org/packages/c8/e4/e6182e4697665fb594a7f4e4f27cb3a4dd00c2e3d35c5c706765de8c7866/coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9"}, + {url = "https://files.pythonhosted.org/packages/ca/0c/3dfeeb1006c44b911ee0ed915350db30325d01808525ae7cc8d57643a2ce/coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2"}, + {url = "https://files.pythonhosted.org/packages/d1/3a/67f5d18f911abf96857f6f7e4df37ca840e38179e2cc9ab6c0b9c3380f19/coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2"}, + {url = "https://files.pythonhosted.org/packages/d9/1d/cd467fceb62c371f9adb1d739c92a05d4e550246daa90412e711226bd320/coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e"}, + {url = "https://files.pythonhosted.org/packages/dd/ce/97c1dd6592c908425622fe7f31c017d11cf0421729b09101d4de75bcadc8/coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5"}, + {url = "https://files.pythonhosted.org/packages/de/a3/5a98dc9e239d0dc5f243ef5053d5b1bdcaa1dee27a691dfc12befeccf878/coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f"}, + {url = "https://files.pythonhosted.org/packages/e2/c0/73f139794c742840b9ab88e2e17fe14a3d4668a166ff95d812ac66c0829d/coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd"}, + {url = "https://files.pythonhosted.org/packages/e9/40/383305500d24122dbed73e505a4d6828f8f3356d1f68ab6d32c781754b81/coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f"}, + {url = "https://files.pythonhosted.org/packages/fe/57/e4f8ad64d84ca9e759d783a052795f62a9f9111585e46068845b1cb52c2b/coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1"}, + {url = "https://files.pythonhosted.org/packages/ff/d5/52fa1891d1802ab2e1b346d37d349cb41cdd4fd03f724ebbf94e80577687/coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4"}, +] +"deprecated 1.2.14" = [ + {url = "https://files.pythonhosted.org/packages/20/8d/778b7d51b981a96554f29136cd59ca7880bf58094338085bcf2a979a0e6a/Deprecated-1.2.14-py2.py3-none-any.whl", hash = "sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c"}, + {url = "https://files.pythonhosted.org/packages/92/14/1e41f504a246fc224d2ac264c227975427a85caf37c3979979edb9b1b232/Deprecated-1.2.14.tar.gz", hash = "sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3"}, +] +"dill 0.3.6" = [ + {url = "https://files.pythonhosted.org/packages/7c/e7/364a09134e1062d4d5ff69b853a56cf61c223e0afcc6906b6832bcd51ea8/dill-0.3.6.tar.gz", hash = "sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373"}, + {url = "https://files.pythonhosted.org/packages/be/e3/a84bf2e561beed15813080d693b4b27573262433fced9c1d1fea59e60553/dill-0.3.6-py3-none-any.whl", hash = "sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0"}, +] +"distlib 0.3.6" = [ + {url = "https://files.pythonhosted.org/packages/58/07/815476ae605bcc5f95c87a62b95e74a1bce0878bc7a3119bc2bf4178f175/distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, + {url = "https://files.pythonhosted.org/packages/76/cb/6bbd2b10170ed991cf64e8c8b85e01f2fb38f95d1bc77617569e0b0b26ac/distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, +] +"exceptiongroup 1.1.2" = [ + {url = "https://files.pythonhosted.org/packages/55/09/5d2079ecab0ca483e527a1707a483562bdc17abf829d3e73f0c1a73b61c7/exceptiongroup-1.1.2.tar.gz", hash = "sha256:12c3e887d6485d16943a309616de20ae5582633e0a2eda17f4e10fd61c1e8af5"}, + {url = "https://files.pythonhosted.org/packages/fe/17/f43b7c9ccf399d72038042ee72785c305f6c6fdc6231942f8ab99d995742/exceptiongroup-1.1.2-py3-none-any.whl", hash = "sha256:e346e69d186172ca7cf029c8c1d16235aa0e04035e5750b4b95039e65204328f"}, +] +"fastapi 0.79.1" = [ + {url = "https://files.pythonhosted.org/packages/29/95/31db8826daec0bfc9a4bb6ceac3ceeb7e8e6c6b81b6d204aaec788445c8c/fastapi-0.79.1-py3-none-any.whl", hash = "sha256:3c584179c64e265749e88221c860520fc512ea37e253282dab378cc503dfd7fd"}, + {url = "https://files.pythonhosted.org/packages/48/eb/feec98de25762193702e909535fbf4a1b3cb63617e6ee2e72c95ae4789ba/fastapi-0.79.1.tar.gz", hash = "sha256:006862dec0f0f5683ac21fb0864af2ff12a931e7ba18920f28cc8eceed51896b"}, +] +"filelock 3.12.2" = [ + {url = "https://files.pythonhosted.org/packages/00/0b/c506e9e44e4c4b6c89fcecda23dc115bf8e7ff7eb127e0cb9c114cbc9a15/filelock-3.12.2.tar.gz", hash = "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81"}, + {url = "https://files.pythonhosted.org/packages/00/45/ec3407adf6f6b5bf867a4462b2b0af27597a26bd3cd6e2534cb6ab029938/filelock-3.12.2-py3-none-any.whl", hash = "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec"}, +] +"grapheme 0.6.0" = [ + {url = "https://files.pythonhosted.org/packages/ce/e7/bbaab0d2a33e07c8278910c1d0d8d4f3781293dfbc70b5c38197159046bf/grapheme-0.6.0.tar.gz", hash = "sha256:44c2b9f21bbe77cfb05835fec230bd435954275267fea1858013b102f8603cca"}, +] +"greenlet 2.0.2" = [ + {url = "https://files.pythonhosted.org/packages/07/ef/6bfa2ea34f76dea02833d66d28ae7cf4729ddab74ee93ee069c7f1d47c4f/greenlet-2.0.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:d5508f0b173e6aa47273bdc0a0b5ba055b59662ba7c7ee5119528f466585526b"}, + {url = "https://files.pythonhosted.org/packages/08/b1/0615df6393464d6819040124eb7bdff6b682f206a464b4537964819dcab4/greenlet-2.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dd11f291565a81d71dab10b7033395b7a3a5456e637cf997a6f33ebdf06f8db"}, + {url = "https://files.pythonhosted.org/packages/09/57/5fdd37939e0989a756a32d0a838409b68d1c5d348115e9c697f42ee4f87d/greenlet-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df"}, + {url = "https://files.pythonhosted.org/packages/09/93/d7ed73f82b6f1045dd5d98f063fa16da5273d0812c42f38229d28882762b/greenlet-2.0.2-cp39-cp39-win32.whl", hash = "sha256:ea9872c80c132f4663822dd2a08d404073a5a9b5ba6155bea72fb2a79d1093b5"}, + {url = "https://files.pythonhosted.org/packages/0a/46/96b37dcfe9c9d39b2d2f060a5775139ce8a440315a1ca2667a6b83a2860e/greenlet-2.0.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df"}, + {url = "https://files.pythonhosted.org/packages/0a/54/cbc1096b883b2d1c0c1454837f089971de814ba5ce42be04cf0716a06000/greenlet-2.0.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0f72c9ddb8cd28532185f54cc1453f2c16fb417a08b53a855c4e6a418edd099"}, + {url = "https://files.pythonhosted.org/packages/0d/f6/2d406a22767029e785154071bef79b296f91b92d1c199ec3c2202386bf04/greenlet-2.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b"}, + {url = "https://files.pythonhosted.org/packages/17/f9/7f5d755380d329e44307c2f6e52096740fdebb92e7e22516811aeae0aec0/greenlet-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91"}, + {url = "https://files.pythonhosted.org/packages/1d/a0/697653ea5e35acaf28e2a1246645ac512beb9b49a86b310fd0151b075342/greenlet-2.0.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6"}, + {url = "https://files.pythonhosted.org/packages/1e/1e/632e55a04d732c8184201238d911207682b119c35cecbb9a573a6c566731/greenlet-2.0.2.tar.gz", hash = "sha256:e7c8dc13af7db097bed64a051d2dd49e9f0af495c26995c00a9ee842690d34c0"}, + {url = "https://files.pythonhosted.org/packages/1f/42/95800f165d20fb8269fe6a3ac494649718ede074b1d8a78f58ee2ebda27a/greenlet-2.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:18e98fb3de7dba1c0a852731c3070cf022d14f0d68b4c87a19cc1016f3bb8b33"}, + {url = "https://files.pythonhosted.org/packages/20/28/c93ffaa75f3c907cd010bf44c5c18c7f8f4bb2409146bd67d538163e33b8/greenlet-2.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1a819eef4b0e0b96bb0d98d797bef17dc1b4a10e8d7446be32d1da33e095dbb8"}, + {url = "https://files.pythonhosted.org/packages/29/c4/fe82cb9ff1bffc52a3832e35fa49cce63e5d366808179153ee879ce47cc9/greenlet-2.0.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9d14b83fab60d5e8abe587d51c75b252bcc21683f24699ada8fb275d7712f5a9"}, + {url = "https://files.pythonhosted.org/packages/37/b9/3ebd606768bee3ef2198fe6d5e7c6c3af42ad3e06b56c1d0a89c56faba2a/greenlet-2.0.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b"}, + {url = "https://files.pythonhosted.org/packages/3a/69/a6d3d7abd0f36438ff5fab52572fd107966939d59ef9b8309263ab89f607/greenlet-2.0.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:910841381caba4f744a44bf81bfd573c94e10b3045ee00de0cbf436fe50673a6"}, + {url = "https://files.pythonhosted.org/packages/42/d0/285b81442d8552b1ae6a2ff38caeec94ab90507c9740da718189416e8e6e/greenlet-2.0.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:bdfea8c661e80d3c1c99ad7c3ff74e6e87184895bbaca6ee8cc61209f8b9b85d"}, + {url = "https://files.pythonhosted.org/packages/43/81/e0a656e3a417b172f834ba5a08dde02b55fd249416c1e933d62ffb6734d0/greenlet-2.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2780572ec463d44c1d3ae850239508dbeb9fed38e294c68d19a24d925d9223ca"}, + {url = "https://files.pythonhosted.org/packages/49/b8/3ee1723978245e6f0c087908689f424876803ec05555400681240ab2ab33/greenlet-2.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be4ed120b52ae4d974aa40215fcdfde9194d63541c7ded40ee12eb4dda57b76b"}, + {url = "https://files.pythonhosted.org/packages/4d/b2/32f737e1fcf67b23351b4860489029df562b41d7ffb568a3e1ae610f7a9b/greenlet-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7efde645ca1cc441d6dc4b48c0f7101e8d86b54c8530141b09fd31cef5149ec9"}, + {url = "https://files.pythonhosted.org/packages/50/3d/7e3d95b955722c514f982bdf6bbe92bb76218b0036dd9b093ae0c168d63a/greenlet-2.0.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30"}, + {url = "https://files.pythonhosted.org/packages/52/39/fa5212bc9ac588c62e52213d4fab30a348059842883410724f9d0408c0f4/greenlet-2.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343"}, + {url = "https://files.pythonhosted.org/packages/53/0f/637f6e18e1980ebd2eedd8a9918a7898a6fe44f6188f6f39c6d9181c9891/greenlet-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645"}, + {url = "https://files.pythonhosted.org/packages/54/ce/3a589ec27bd5de97707d2a193716bbe412ccbdb1479f0c3f990789c8fa8c/greenlet-2.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9c59a2120b55788e800d82dfa99b9e156ff8f2227f07c5e3012a45a399620b7"}, + {url = "https://files.pythonhosted.org/packages/57/a8/079c59b8f5406957224f4f4176e9827508d555beba6d8635787d694226d1/greenlet-2.0.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:18a7f18b82b52ee85322d7a7874e676f34ab319b9f8cce5de06067384aa8ff43"}, + {url = "https://files.pythonhosted.org/packages/5a/30/5eab5cbb99263c7d8305657587381c84da2a71fddb07dd5efbfaeecf7264/greenlet-2.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937e9020b514ceedb9c830c55d5c9872abc90f4b5862f89c0887033ae33c6f73"}, + {url = "https://files.pythonhosted.org/packages/6a/3d/77bd8dd7dd0b872eac87f1edf6fcd94d9d7666befb706ae3a08ed25fbea7/greenlet-2.0.2-cp36-cp36m-win32.whl", hash = "sha256:dbfcfc0218093a19c252ca8eb9aee3d29cfdcb586df21049b9d777fd32c14fd9"}, + {url = "https://files.pythonhosted.org/packages/6b/2f/1cb3f376df561c95cb61b199676f51251f991699e325a2aa5e12693d10b8/greenlet-2.0.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0"}, + {url = "https://files.pythonhosted.org/packages/6b/cd/84301cdf80360571f6aa77ac096f867ba98094fec2cb93e69c93d996b8f8/greenlet-2.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a"}, + {url = "https://files.pythonhosted.org/packages/6e/11/a1f1af20b6a1a8069bc75012569d030acb89fd7ef70f888b6af2f85accc6/greenlet-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d75209eed723105f9596807495d58d10b3470fa6732dd6756595e89925ce2470"}, + {url = "https://files.pythonhosted.org/packages/71/c5/c26840ce91bcbbfc42c1a246289d9d4c758663652669f24e37f84bcdae2a/greenlet-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5454276c07d27a740c5892f4907c86327b632127dd9abec42ee62e12427ff7e3"}, + {url = "https://files.pythonhosted.org/packages/7c/5f/ee39d27a08ae6b93f14faa953a6593dad888df75ae55ff479135e64ad4fe/greenlet-2.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acd2162a36d3de67ee896c43effcd5ee3de247eb00354db411feb025aa319857"}, + {url = "https://files.pythonhosted.org/packages/7c/f8/275f7fb1585d5e7dfbc18b4eb78282fbc85986f2eb8a185e7ebc60522cc2/greenlet-2.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:703f18f3fda276b9a916f0934d2fb6d989bf0b4fb5a64825260eb9bfd52d78f0"}, + {url = "https://files.pythonhosted.org/packages/7e/a6/0a34cde83fe520fa4e8192a1bc0fc7bf9f755215fefe3f42c9b97c45c620/greenlet-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:7cafd1208fdbe93b67c7086876f061f660cfddc44f404279c1585bbf3cdc64c5"}, + {url = "https://files.pythonhosted.org/packages/83/d1/cc273f8f5908940d6666a3db8637d2e24913a2e8e5034012b19ac291a2a0/greenlet-2.0.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:3c9b12575734155d0c09d6c3e10dbd81665d5c18e1a7c6597df72fd05990c8cf"}, + {url = "https://files.pythonhosted.org/packages/86/8d/3a18311306830f6db5f5676a1cb8082c8943bfa6c928b40006e5358170fc/greenlet-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2"}, + {url = "https://files.pythonhosted.org/packages/93/40/db2803f88326149ddcd1c00092e1e36ef55d31922812863753143a9aca01/greenlet-2.0.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd021c754b162c0fb55ad5d6b9d960db667faad0fa2ff25bb6e1301b0b6e6a75"}, + {url = "https://files.pythonhosted.org/packages/9d/ae/8ee23a9b63f854acc66ed0da7220130d87c861153cbc8ea07d11b61567f1/greenlet-2.0.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b9ec052b06a0524f0e35bd8790686a1da006bd911dd1ef7d50b77bfbad74e292"}, + {url = "https://files.pythonhosted.org/packages/a1/ea/66e69cf3034be99a1959b2bdd178f5176979e0e63107a37a194c90c49b40/greenlet-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40"}, + {url = "https://files.pythonhosted.org/packages/a3/6c/dde49c63ab2f12d2ce401620dbe1a80830109f5f310bdd2f96d2e259de37/greenlet-2.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:9f35ec95538f50292f6d8f2c9c9f8a3c6540bbfec21c9e5b4b751e0a7c20864f"}, + {url = "https://files.pythonhosted.org/packages/a8/7a/5542d863a91b3309585219bae7d97aa82fe0482499a840c100297262ec8f/greenlet-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c"}, + {url = "https://files.pythonhosted.org/packages/aa/21/6bbd8062fee551f747f5334b7ccd503693704ac4f3183fd8232e2af77bff/greenlet-2.0.2-cp35-cp35m-win32.whl", hash = "sha256:03a8f4f3430c3b3ff8d10a2a86028c660355ab637cee9333d63d66b56f09d52a"}, + {url = "https://files.pythonhosted.org/packages/ac/4a/3ceafef892b8428f77468506bc5a12d835fb9f150129d1a9704902cb4a2a/greenlet-2.0.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4b58adb399c4d61d912c4c331984d60eb66565175cdf4a34792cd9600f21b394"}, + {url = "https://files.pythonhosted.org/packages/b3/89/1d3b78577a6b2762cb254f6ce5faec9b7c7b23052d1cdb7237273ff37d10/greenlet-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:db1a39669102a1d8d12b57de2bb7e2ec9066a6f2b3da35ae511ff93b01b5d564"}, + {url = "https://files.pythonhosted.org/packages/c4/92/bbd9373fb022c21d1c41bc74b043d8d007825f80bb9534f0dd2f7ed62bca/greenlet-2.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a51c9751078733d88e013587b108f1b7a1fb106d402fb390740f002b6f6551a"}, + {url = "https://files.pythonhosted.org/packages/c5/ab/a69a875a45474cc5776b879258bfa685e99aae992ab310a0b8f773fe56a0/greenlet-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088"}, + {url = "https://files.pythonhosted.org/packages/c7/c9/2637e49b0ef3f17d7eaa52c5af5bfbda5f058e8ee97bd9418978b90e1169/greenlet-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526"}, + {url = "https://files.pythonhosted.org/packages/ca/1a/90f2ae7e3df48cbd42af5df47cf9ee37a6c6a78b1941acbc7eac029f5a44/greenlet-2.0.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae"}, + {url = "https://files.pythonhosted.org/packages/cd/e8/1ebc8f07d795c3677247e37dae23463a655636a4be387b0d739fa8fd9b2f/greenlet-2.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb"}, + {url = "https://files.pythonhosted.org/packages/d2/28/5cf37650334935c6a51313c70c4ec00fb1fad801a551c36afcfc9c03e80b/greenlet-2.0.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:32e5b64b148966d9cccc2c8d35a671409e45f195864560829f395a54226408d3"}, + {url = "https://files.pythonhosted.org/packages/d6/c4/f91d771a6628155676765c419c70d6d0ede9b5f3c023102c47ee2f45eadf/greenlet-2.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:36abbf031e1c0f79dd5d596bfaf8e921c41df2bdf54ee1eed921ce1f52999a86"}, + {url = "https://files.pythonhosted.org/packages/da/45/2600faf65f318767d2c24b6fce6bb0ad3721e8cb3eb9d7743aefcca8a6a6/greenlet-2.0.2-cp38-cp38-win32.whl", hash = "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249"}, + {url = "https://files.pythonhosted.org/packages/e5/ad/91a8f63881c862bb396cefc33d7faa241bf200df7ba96a1961a99329ed15/greenlet-2.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0bf60faf0bc2468089bdc5edd10555bab6e85152191df713e2ab1fcc86382b5a"}, + {url = "https://files.pythonhosted.org/packages/e6/0e/591ea935b63aa3aed3836976779e5d1324aa4b2961f7355ff5d1f296066b/greenlet-2.0.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:f82d4d717d8ef19188687aa32b8363e96062911e63ba22a0cff7802a8e58e5f1"}, + {url = "https://files.pythonhosted.org/packages/e8/3a/ebc4fa1e813ae1fa718eb88417c31587e2efb743ed5f6ff0ae066502c349/greenlet-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca"}, + {url = "https://files.pythonhosted.org/packages/e9/29/2ae545c4c0218b042c2bb0760c0f65e114cca1ab5e552dc23b0f118e428a/greenlet-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94c817e84245513926588caf1152e3b559ff794d505555211ca041f032abbb6b"}, + {url = "https://files.pythonhosted.org/packages/f0/2e/20eab0fa6353a08b0de055dd54e2575a6869ee693d86387076430475832d/greenlet-2.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:eff4eb9b7eb3e4d0cae3d28c283dc16d9bed6b193c2e1ace3ed86ce48ea8df19"}, + {url = "https://files.pythonhosted.org/packages/f4/ad/287efe1d3c8224fa5f9457195a842fc0c4fa4956cb9655a1f4e89914a313/greenlet-2.0.2-cp27-cp27m-win32.whl", hash = "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74"}, + {url = "https://files.pythonhosted.org/packages/f6/04/74e97d545f9276dee994b959eab3f7d70d77588e5aaedc383d15b0057acd/greenlet-2.0.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8"}, + {url = "https://files.pythonhosted.org/packages/fa/9a/e0e99a4aa93b16dd58881acb55ac1e2fb011475f2e46cf87843970001882/greenlet-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3"}, + {url = "https://files.pythonhosted.org/packages/fc/80/0ed0da38bbb978f39128d7e53ee51c36bed2e4a7460eff92981a3d07f1d4/greenlet-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7"}, +] +"identify 2.5.24" = [ + {url = "https://files.pythonhosted.org/packages/4f/fd/2c46fba2bc032ba4c970bb8de59d25187087d7138a0ebf7c1dcc91d94f01/identify-2.5.24-py2.py3-none-any.whl", hash = "sha256:986dbfb38b1140e763e413e6feb44cd731faf72d1909543178aa79b0e258265d"}, + {url = "https://files.pythonhosted.org/packages/c4/f8/498e13e408d25ee6ff04aa0acbf91ad8e9caae74be91720fc0e811e649b7/identify-2.5.24.tar.gz", hash = "sha256:0aac67d5b4812498056d28a9a512a483f5085cc28640b02b258a59dac34301d4"}, +] +"idna 3.4" = [ + {url = "https://files.pythonhosted.org/packages/8b/e1/43beb3d38dba6cb420cefa297822eac205a277ab43e5ba5d5c46faf96438/idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, + {url = "https://files.pythonhosted.org/packages/fc/34/3030de6f1370931b9dbb4dad48f6ab1015ab1d32447850b9fc94e60097be/idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, +] +"importlib-metadata 6.0.1" = [ + {url = "https://files.pythonhosted.org/packages/30/3b/83a992fe6db1dd8de6e88cffa9481516e6984d63983f88cc031fe1bb992d/importlib_metadata-6.0.1.tar.gz", hash = "sha256:950127d57e35a806d520817d3e92eec3f19fdae9f0cd99da77a407c5aabefba3"}, + {url = "https://files.pythonhosted.org/packages/c0/b4/776f9148826bf17465fc9ed3b503ecc2073c8700017b9abdff1c57cc31ad/importlib_metadata-6.0.1-py3-none-any.whl", hash = "sha256:1543daade821c89b1c4a55986c326f36e54f2e6ca3bad96be4563d0acb74dcd4"}, +] +"importlib-resources 5.12.0" = [ + {url = "https://files.pythonhosted.org/packages/38/71/c13ea695a4393639830bf96baea956538ba7a9d06fcce7cef10bfff20f72/importlib_resources-5.12.0-py3-none-any.whl", hash = "sha256:7b1deeebbf351c7578e09bf2f63fa2ce8b5ffec296e0d349139d43cca061a81a"}, + {url = "https://files.pythonhosted.org/packages/4e/a2/3cab1de83f95dd15297c15bdc04d50902391d707247cada1f021bbfe2149/importlib_resources-5.12.0.tar.gz", hash = "sha256:4be82589bf5c1d7999aedf2a45159d10cb3ca4f19b2271f8792bc8e6da7b22f6"}, +] +"iniconfig 2.0.0" = [ + {url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, + {url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, +] +"isort 5.12.0" = [ + {url = "https://files.pythonhosted.org/packages/0a/63/4036ae70eea279c63e2304b91ee0ac182f467f24f86394ecfe726092340b/isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"}, + {url = "https://files.pythonhosted.org/packages/a9/c4/dc00e42c158fc4dda2afebe57d2e948805c06d5169007f1724f0683010a9/isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"}, +] +"kombu 5.3.1" = [ + {url = "https://files.pythonhosted.org/packages/63/58/b23b9c1ffb30d8b5cdfc7bdecb17bfd7ea20c619e86e515297b496177144/kombu-5.3.1-py3-none-any.whl", hash = "sha256:48ee589e8833126fd01ceaa08f8a2041334e9f5894e5763c8486a550454551e9"}, + {url = "https://files.pythonhosted.org/packages/c8/69/b703f8ec8d0406be22534dad885cac847fe092b793c4893034e3308feb9b/kombu-5.3.1.tar.gz", hash = "sha256:fbd7572d92c0bf71c112a6b45163153dea5a7b6a701ec16b568c27d0fd2370f2"}, +] +"lazy-object-proxy 1.9.0" = [ + {url = "https://files.pythonhosted.org/packages/00/74/46a68f51457639c0cd79e385e2f49c0fa7324470997ac096108669c1e182/lazy_object_proxy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:946d27deaff6cf8452ed0dba83ba38839a87f4f7a9732e8f9fd4107b21e6ff07"}, + {url = "https://files.pythonhosted.org/packages/11/04/fa820296cb937b378d801cdc81c19de06624cfed481c1b8a6b439255a188/lazy_object_proxy-1.9.0-cp37-cp37m-win32.whl", hash = "sha256:f12ad7126ae0c98d601a7ee504c1122bcef553d1d5e0c3bfa77b16b3968d2734"}, + {url = "https://files.pythonhosted.org/packages/11/fe/be1eb76d83f1b5242c492b410ce86c59db629c0b0f0f8e75018dfd955c30/lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f5fa4a61ce2438267163891961cfd5e32ec97a2c444e5b842d574251ade27d2"}, + {url = "https://files.pythonhosted.org/packages/16/f2/e74981dedeb1a858cd5db9bcec81c4107da374249bc6894613472e01996f/lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0117049dd1d5635bbff65444496c90e0baa48ea405125c088e93d9cf4525b11"}, + {url = "https://files.pythonhosted.org/packages/18/1b/04ac4490a62ae1916f88e629e74192ada97d74afc927453d005f003e5a8f/lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f83ac4d83ef0ab017683d715ed356e30dd48a93746309c8f3517e1287523ef4"}, + {url = "https://files.pythonhosted.org/packages/1d/5d/25b9007c65f45805e711b56beac50ba395214e9e556cc8ee57f0882f88a9/lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8c6cfb338b133fbdbc5cfaa10fe3c6aeea827db80c978dbd13bc9dd8526b7d4"}, + {url = "https://files.pythonhosted.org/packages/20/c0/8bab72a73607d186edad50d0168ca85bd2743cfc55560c9d721a94654b20/lazy-object-proxy-1.9.0.tar.gz", hash = "sha256:659fb5809fa4629b8a1ac5106f669cfc7bef26fbb389dda53b3e010d1ac4ebae"}, + {url = "https://files.pythonhosted.org/packages/27/a1/7cc10ca831679c5875c18ae6e0a468f7787ecd31fdd53598f91ea50df58d/lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:721532711daa7db0d8b779b0bb0318fa87af1c10d7fe5e52ef30f8eff254d0cd"}, + {url = "https://files.pythonhosted.org/packages/31/ad/e8605300f51061284cc57ca0f4ef582047c7f309bda1bb1c3c19b64af5c9/lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66a3de4a3ec06cd8af3f61b8e1ec67614fbb7c995d02fa224813cb7afefee701"}, + {url = "https://files.pythonhosted.org/packages/4c/a4/cdd6f41a675a89ab584c78019a24adc533829764bcc85b0e24ed2678020c/lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7322c3d6f1766d4ef1e51a465f47955f1e8123caee67dd641e67d539a534d006"}, + {url = "https://files.pythonhosted.org/packages/4d/7b/a959ff734bd3d8df7b761bfeaec6428549b77267072676a337b774f3b3ef/lazy_object_proxy-1.9.0-cp310-cp310-win32.whl", hash = "sha256:f0705c376533ed2a9e5e97aacdbfe04cecd71e0aa84c7c0595d02ef93b6e4455"}, + {url = "https://files.pythonhosted.org/packages/4e/cb/aca3f4d89d3efbed724fd9504a96dafbe2d903ea908355a335acb110a5cd/lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:660c94ea760b3ce47d1855a30984c78327500493d396eac4dfd8bd82041b22be"}, + {url = "https://files.pythonhosted.org/packages/51/28/5c6dfb51df2cbb6d771a3b0d009f1edeab01f5cb16303ce32764b01636c0/lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfb38f9ffb53b942f2b5954e0f610f1e721ccebe9cce9025a38c8ccf4a5183a4"}, + {url = "https://files.pythonhosted.org/packages/5b/a6/3c0a8b2ad6ce7af133ed54321b0ead5509303be3a80f98506af198e50cb7/lazy_object_proxy-1.9.0-cp38-cp38-win32.whl", hash = "sha256:0a891e4e41b54fd5b8313b96399f8b0e173bbbfc03c7631f01efbe29bb0bcf82"}, + {url = "https://files.pythonhosted.org/packages/5c/76/0b16dc53e9ee5b24c621d808f46cca11e5e86c602b6bcd6dc27f9504af5b/lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:09763491ce220c0299688940f8dc2c5d05fd1f45af1e42e636b2e8b2303e4382"}, + {url = "https://files.pythonhosted.org/packages/69/1f/51657d681711476287c9ff643428be0f9663addc1d341d4be1bad89290bd/lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e7c21c95cae3c05c14aafffe2865bbd5e377cfc1348c4f7751d9dc9a48ca4bda"}, + {url = "https://files.pythonhosted.org/packages/69/da/58391196d8a41fa8fa69b47e8a7893f279d369939e4994b3bc8648ff0433/lazy_object_proxy-1.9.0-cp39-cp39-win32.whl", hash = "sha256:9090d8e53235aa280fc9239a86ae3ea8ac58eff66a705fa6aa2ec4968b95c821"}, + {url = "https://files.pythonhosted.org/packages/70/e7/f3735f8e47cb29a207568c5b8d28d9f5673228789b66cb0c48d488a37f94/lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:212774e4dfa851e74d393a2370871e174d7ff0ebc980907723bb67d25c8a7c30"}, + {url = "https://files.pythonhosted.org/packages/82/ac/d079d3ad377ba72e29d16ac077f8626ad4d3f55369c93168d0b81153d9a2/lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:18b78ec83edbbeb69efdc0e9c1cb41a3b1b1ed11ddd8ded602464c3fc6020494"}, + {url = "https://files.pythonhosted.org/packages/86/93/e921f7a795e252df7248e0d220dc69a9443ad507fe258dea51a32e5435ca/lazy_object_proxy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b40387277b0ed2d0602b8293b94d7257e17d1479e257b4de114ea11a8cb7f2d7"}, + {url = "https://files.pythonhosted.org/packages/8d/6d/10420823a979366bf43ca5e69433c0c588865883566b96b6e3ed5b51c1f8/lazy_object_proxy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:f2457189d8257dd41ae9b434ba33298aec198e30adf2dcdaaa3a28b9994f6adb"}, + {url = "https://files.pythonhosted.org/packages/9d/d7/81d466f2e69784bd416797824a2b8794aaf0b864a2390062ea197f06f0fc/lazy_object_proxy-1.9.0-cp311-cp311-win32.whl", hash = "sha256:81fc4d08b062b535d95c9ea70dbe8a335c45c04029878e62d744bdced5141586"}, + {url = "https://files.pythonhosted.org/packages/a7/51/6626c133e966698d53d65bcd90e34ad4986c5f0968c2328b3e9de51dbcf1/lazy_object_proxy-1.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9e25ef10a39e8afe59a5c348a4dbf29b4868ab76269f81ce1674494e2565a6e"}, + {url = "https://files.pythonhosted.org/packages/a8/32/c1a67f76ec6923a8a8b1bc006b7cb3d195e386e03fe56e20fe38fce0321e/lazy_object_proxy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9e7551208b2aded9c1447453ee366f1c4070602b3d932ace044715d89666899b"}, + {url = "https://files.pythonhosted.org/packages/b0/78/78962cb6f6d31a952acbc54e7838a5a85d952144973bd6e7ad24323dd466/lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:189bbd5d41ae7a498397287c408617fe5c48633e7755287b21d741f7db2706a9"}, + {url = "https://files.pythonhosted.org/packages/b1/80/2d9583fa8e5ac47ef183d811d26d833477b7ed02b64c17dd2ede68a3f9cf/lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cbf9b082426036e19c6924a9ce90c740a9861e2bdc27a4834fd0a910742ac1e8"}, + {url = "https://files.pythonhosted.org/packages/c9/8f/c8aab72c72634de0c726a98a1e4c84a93ef20049ee0427c871214f6a58d5/lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f699ac1c768270c9e384e4cbd268d6e67aebcfae6cd623b4d7c3bfde5a35db59"}, + {url = "https://files.pythonhosted.org/packages/cd/b6/84efe6e878e94f20cf9564ac3ede5e98d37c692b07080aef50cc4341052e/lazy_object_proxy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0daa332786cf3bb49e10dc6a17a52f6a8f9601b4cf5c295a4f85854d61de63"}, + {url = "https://files.pythonhosted.org/packages/d0/f4/95999792ce5f9223bac10cb31b1724723ecd0ba13e081c5fb806d7f5b9c4/lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1aa3de4088c89a1b69f8ec0dcc169aa725b0ff017899ac568fe44ddc1396df46"}, + {url = "https://files.pythonhosted.org/packages/db/92/284ab10a6d0f82da36a20d9c1464c30bb318d1a6dd0ae476de9f890e7abd/lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8fa02eaab317b1e9e03f69aab1f91e120e7899b392c4fc19807a8278a07a97e8"}, + {url = "https://files.pythonhosted.org/packages/e7/86/ec93d495197f1006d7c9535e168fe763b3cc21928fb35c8f9ce08944b614/lazy_object_proxy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:ea806fd4c37bf7e7ad82537b0757999264d5f70c45468447bb2b91afdbe73a6e"}, + {url = "https://files.pythonhosted.org/packages/ed/9b/44c370c8bbba32fd0217b4f15ca99f750d669d653c7f1eefa051627710e8/lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cd077f3d04a58e83d04b20e334f678c2b0ff9879b9375ed107d5d07ff160171"}, + {url = "https://files.pythonhosted.org/packages/f5/4f/9ad496dc26a10ed9ab8f088732f08dc1f88488897d6c9ac5e3432a254c30/lazy_object_proxy-1.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:edd20c5a55acb67c7ed471fa2b5fb66cb17f61430b7a6b9c3b4a1e40293b1671"}, + {url = "https://files.pythonhosted.org/packages/fb/f4/c5d6d771e70ec7a9483a98054e8a5f386eda5b18b6c96544d251558c6c92/lazy_object_proxy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:9990d8e71b9f6488e91ad25f322898c136b008d87bf852ff65391b004da5e17b"}, + {url = "https://files.pythonhosted.org/packages/fc/8d/8e0fbfeec6e51184326e0886443e44142ce22d89fa9e9c3152900e654fa0/lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79a31b086e7e68b24b99b23d57723ef7e2c6d81ed21007b6281ebcd1688acb0a"}, + {url = "https://files.pythonhosted.org/packages/fe/bb/0fcc8585ffb7285df94382e20b54d54ca62a1bcf594f6f18d8feb3fc3b98/lazy_object_proxy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:db1c1722726f47e10e0b5fdbf15ac3b8adb58c091d12b3ab713965795036985f"}, +] +"mako 1.2.4" = [ + {url = "https://files.pythonhosted.org/packages/03/3b/68690a035ba7347860f1b8c0cde853230ba69ff41df5884ea7d89fe68cd3/Mako-1.2.4-py3-none-any.whl", hash = "sha256:c97c79c018b9165ac9922ae4f32da095ffd3c4e6872b45eded42926deea46818"}, + {url = "https://files.pythonhosted.org/packages/05/5f/2ba6e026d33a0e6ddc1dddf9958677f76f5f80c236bd65309d280b166d3e/Mako-1.2.4.tar.gz", hash = "sha256:d60a3903dc3bb01a18ad6a89cdbe2e4eadc69c0bc8ef1e3773ba53d44c3f7a34"}, +] +"markdown-it-py 3.0.0" = [ + {url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] +"markupsafe 2.1.3" = [ + {url = "https://files.pythonhosted.org/packages/03/06/e72e88f81f8c91d4f488d21712d2d403fd644e3172eaadc302094377bc22/MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, + {url = "https://files.pythonhosted.org/packages/03/65/3473d2cb84bb2cda08be95b97fc4f53e6bcd701a2d50ba7b7c905e1e9273/MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, + {url = "https://files.pythonhosted.org/packages/10/b3/c2b0a61cc0e1d50dd8a1b663ba4866c667cb58fb35f12475001705001680/MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, + {url = "https://files.pythonhosted.org/packages/12/b3/d9ed2c0971e1435b8a62354b18d3060b66c8cb1d368399ec0b9baa7c0ee5/MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, + {url = "https://files.pythonhosted.org/packages/20/1d/713d443799d935f4d26a4f1510c9e61b1d288592fb869845e5cc92a1e055/MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, + {url = "https://files.pythonhosted.org/packages/22/81/b5659e2b6ae1516495a22f87370419c1d79c8d853315e6cbe5172fc01a06/MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, + {url = "https://files.pythonhosted.org/packages/32/d4/ce98c4ca713d91c4a17c1a184785cc00b9e9c25699d618956c2b9999500a/MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, + {url = "https://files.pythonhosted.org/packages/3c/c8/74d13c999cbb49e3460bf769025659a37ef4a8e884de629720ab4e42dcdb/MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, + {url = "https://files.pythonhosted.org/packages/43/70/f24470f33b2035b035ef0c0ffebf57006beb2272cf3df068fc5154e04ead/MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, + {url = "https://files.pythonhosted.org/packages/43/ad/7246ae594aac948b17408c0ff0f9ff0bc470bdbe9c672a754310db64b237/MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, + {url = "https://files.pythonhosted.org/packages/44/53/93405d37bb04a10c43b1bdd6f548097478d494d7eadb4b364e3e1337f0cc/MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, + {url = "https://files.pythonhosted.org/packages/47/26/932140621773bfd4df3223fbdd9e78de3477f424f0d2987c313b1cb655ff/MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, + {url = "https://files.pythonhosted.org/packages/4d/e4/77bb622d6a37aeb51ee55857100986528b7f47d6dbddc35f9b404622ed50/MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, + {url = "https://files.pythonhosted.org/packages/4f/13/cf36eff21600fb21d5bd8c4c1b6ff0b7cc0ff37b955017210cfc6f367972/MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, + {url = "https://files.pythonhosted.org/packages/62/9b/4908a57acf39d8811836bc6776b309c2e07d63791485589acf0b6d7bc0c6/MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, + {url = "https://files.pythonhosted.org/packages/68/8d/c33c43c499c19f4b51181e196c9a497010908fc22c5de33551e298aa6a21/MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, + {url = "https://files.pythonhosted.org/packages/6a/86/654dc431513cd4417dfcead8102f22bece2d6abf2f584f0e1cc1524f7b94/MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, + {url = "https://files.pythonhosted.org/packages/6d/7c/59a3248f411813f8ccba92a55feaac4bf360d29e2ff05ee7d8e1ef2d7dbf/MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, + {url = "https://files.pythonhosted.org/packages/71/61/f5673d7aac2cf7f203859008bb3fc2b25187aa330067c5e9955e5c5ebbab/MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, + {url = "https://files.pythonhosted.org/packages/74/a3/54fc60ee2da3ab6d68b1b2daf4897297c597840212ee126e68a4eb89fcd7/MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, + {url = "https://files.pythonhosted.org/packages/7d/48/6ba4db436924698ca22109325969e00be459d417830dafec3c1001878b57/MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, + {url = "https://files.pythonhosted.org/packages/84/a8/c4aebb8a14a1d39d5135eb8233a0b95831cdc42c4088358449c3ed657044/MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, + {url = "https://files.pythonhosted.org/packages/8b/bb/72ca339b012054a84753accabe3258e0baf6e34bd0ab6e3670b9a65f679d/MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, + {url = "https://files.pythonhosted.org/packages/8d/66/4a46c7f1402e0377a8b220fd4b53cc4f1b2337ab0d97f06e23acd1f579d1/MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, + {url = "https://files.pythonhosted.org/packages/96/e4/4db3b1abc5a1fe7295aa0683eafd13832084509c3b8236f3faf8dd4eff75/MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, + {url = "https://files.pythonhosted.org/packages/9b/c1/9f44da5ca74f95116c644892152ca6514ecdc34c8297a3f40d886147863d/MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, + {url = "https://files.pythonhosted.org/packages/a2/b2/624042cb58cc6b3529a6c3a7b7d230766e3ecb768cba118ba7befd18ed6f/MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, + {url = "https://files.pythonhosted.org/packages/a2/f7/9175ad1b8152092f7c3b78c513c1bdfe9287e0564447d1c2d3d1a2471540/MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, + {url = "https://files.pythonhosted.org/packages/a6/56/f1d4ee39e898a9e63470cbb7fae1c58cce6874f25f54220b89213a47f273/MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, + {url = "https://files.pythonhosted.org/packages/a8/12/fd9ef3e09a7312d60467c71037283553ff2acfcd950159cd4c3ca9558af4/MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, + {url = "https://files.pythonhosted.org/packages/ab/20/f59423543a8422cb8c69a579ebd0ef2c9dafa70cc8142b7372b5b4073caa/MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, + {url = "https://files.pythonhosted.org/packages/b2/0d/cbaade3ee8efbd5ce2fb72b48cc51479ebf3d4ce2c54dcb6557d3ea6a950/MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, + {url = "https://files.pythonhosted.org/packages/b2/27/07e5aa9f93314dc65ad2ad9b899656dee79b70a9425ee199dd5a4c4cf2cd/MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, + {url = "https://files.pythonhosted.org/packages/bb/82/f88ccb3ca6204a4536cf7af5abdad7c3657adac06ab33699aa67279e0744/MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, + {url = "https://files.pythonhosted.org/packages/be/bb/08b85bc194034efbf572e70c3951549c8eca0ada25363afc154386b5390a/MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {url = "https://files.pythonhosted.org/packages/bf/b7/c5ba9b7ad9ad21fc4a60df226615cf43ead185d328b77b0327d603d00cc5/MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, + {url = "https://files.pythonhosted.org/packages/c0/c7/171f5ac6b065e1425e8fabf4a4dfbeca76fd8070072c6a41bd5c07d90d8b/MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, + {url = "https://files.pythonhosted.org/packages/c9/80/f08e782943ee7ae6e9438851396d00a869f5b50ea8c6e1f40385f3e95771/MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, + {url = "https://files.pythonhosted.org/packages/d2/a1/4ae49dd1520c7b891ea4963258aab08fb2554c564781ecb2a9c4afdf9cb1/MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, + {url = "https://files.pythonhosted.org/packages/d5/c1/1177f712d4ab91eb67f79d763a7b5f9c5851ee3077d6b4eee15e23b6b93e/MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, + {url = "https://files.pythonhosted.org/packages/de/63/cb7e71984e9159ec5f45b5e81e896c8bdd0e45fe3fc6ce02ab497f0d790e/MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, + {url = "https://files.pythonhosted.org/packages/de/e2/32c14301bb023986dff527a49325b6259cab4ebb4633f69de54af312fc45/MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, + {url = "https://files.pythonhosted.org/packages/e5/dd/49576e803c0d974671e44fa78049217fcc68af3662a24f831525ed30e6c7/MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, + {url = "https://files.pythonhosted.org/packages/e6/5c/8ab8f67bbbbf90fe88f887f4fa68123435c5415531442e8aefef1e118d5c/MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, + {url = "https://files.pythonhosted.org/packages/f4/a0/103f94793c3bf829a18d2415117334ece115aeca56f2df1c47fa02c6dbd6/MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, + {url = "https://files.pythonhosted.org/packages/f7/9c/86cbd8e0e1d81f0ba420f20539dd459c50537c7751e28102dbfee2b6f28c/MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, + {url = "https://files.pythonhosted.org/packages/f8/33/e9e83b214b5f8d9a60b26e60051734e7657a416e5bce7d7f1c34e26badad/MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, + {url = "https://files.pythonhosted.org/packages/fa/bb/12fb5964c4a766eb98155dd31ec070adc8a69a395564ffc1e7b34d91335a/MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, + {url = "https://files.pythonhosted.org/packages/fe/09/c31503cb8150cf688c1534a7135cc39bb9092f8e0e6369ec73494d16ee0e/MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, + {url = "https://files.pythonhosted.org/packages/fe/21/2eff1de472ca6c99ec3993eab11308787b9879af9ca8bbceb4868cf4f2ca/MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, +] +"mccabe 0.7.0" = [ + {url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] +"mdurl 0.1.2" = [ + {url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] +"msgpack 1.0.5" = [ + {url = "https://files.pythonhosted.org/packages/0a/04/bc319ba061f6dc9077745988be288705b3f9f18c5a209772a3e8fcd419fd/msgpack-1.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9985b214f33311df47e274eb788a5893a761d025e2b92c723ba4c63936b69b1"}, + {url = "https://files.pythonhosted.org/packages/0d/90/44edef4a8c6f035b054c4b017c5adcb22a35ec377e17e50dd5dced279a6b/msgpack-1.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:916723458c25dfb77ff07f4c66aed34e47503b2eb3188b3adbec8d8aa6e00f48"}, + {url = "https://files.pythonhosted.org/packages/0e/69/3d10e741dd2bbb806af5cdc76551735baab5f5f9773701eb05502c913a6e/msgpack-1.0.5-cp311-cp311-win32.whl", hash = "sha256:c396e2cc213d12ce017b686e0f53497f94f8ba2b24799c25d913d46c08ec422c"}, + {url = "https://files.pythonhosted.org/packages/10/ca/50c3a5e92d459a942169747315afd8c226d05427eccff903ddf33135c574/msgpack-1.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20a97bf595a232c3ee6d57ddaadd5453d174a52594bf9c21d10407e2a2d9b3bd"}, + {url = "https://files.pythonhosted.org/packages/10/fe/9e004c4deb457f1ef1ad88c1188da5691ff1855e0d03a5ac3635ae1f6530/msgpack-1.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:55b56a24893105dc52c1253649b60f475f36b3aa0fc66115bffafb624d7cb30b"}, + {url = "https://files.pythonhosted.org/packages/12/6e/0cfd1dc07f61a6ac606587a393f489c3ca463469d285a73c8e5e2f61b021/msgpack-1.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:20c784e66b613c7f16f632e7b5e8a1651aa5702463d61394671ba07b2fc9e025"}, + {url = "https://files.pythonhosted.org/packages/17/10/be97811782473d709d07b65a3955a5a76d47686aff3d62bb41d48aea7c92/msgpack-1.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:362d9655cd369b08fda06b6657a303eb7172d5279997abe094512e919cf74b11"}, + {url = "https://files.pythonhosted.org/packages/18/3f/3860151fbdf50e369bbe4ffd307a588417669c725025e383f3ce5893690f/msgpack-1.0.5-cp39-cp39-win32.whl", hash = "sha256:ac9dd47af78cae935901a9a500104e2dea2e253207c924cc95de149606dc43cc"}, + {url = "https://files.pythonhosted.org/packages/19/0c/2c3b443df88f5d400f2e19a3d867564d004b26e137f18c2f2663913987bc/msgpack-1.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:476a8fe8fae289fdf273d6d2a6cb6e35b5a58541693e8f9f019bfe990a51e4ba"}, + {url = "https://files.pythonhosted.org/packages/1a/f7/df5814697c25bdebb14ea97d27ddca04f5d4c6e249f096d086fea521c139/msgpack-1.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b72d0698f86e8d9ddf9442bdedec15b71df3598199ba33322d9711a19f08145c"}, + {url = "https://files.pythonhosted.org/packages/27/ad/4edfe383ec3185611441179ffee8cbc8155d7575fbad73f6d31015e35451/msgpack-1.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9f5ae84c5c8a857ec44dc180a8b0cc08238e021f57abdf51a8182e915e6299f0"}, + {url = "https://files.pythonhosted.org/packages/28/8f/c58c53c884217cc572c19349c7e1129b5a6eae36df0a017aae3a8f3d7aa8/msgpack-1.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:addab7e2e1fcc04bd08e4eb631c2a90960c340e40dfc4a5e24d2ff0d5a3b3edb"}, + {url = "https://files.pythonhosted.org/packages/29/56/1fb6b96aab759ab3bc05b03ba6d936b350db72aac203cde56ea6bd001237/msgpack-1.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d25dd59bbbbb996eacf7be6b4ad082ed7eacc4e8f3d2df1ba43822da9bfa122a"}, + {url = "https://files.pythonhosted.org/packages/2b/c4/f2c8695ae69d1425eddc5e2f849c525b562dc8409bc2979e525f3dd4fecd/msgpack-1.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56a62ec00b636583e5cb6ad313bbed36bb7ead5fa3a3e38938503142c72cba4f"}, + {url = "https://files.pythonhosted.org/packages/2b/d4/9165cf113f9b86ce55e36f36bc6cd9e0c5601b0ade02741b2ead8b5dc254/msgpack-1.0.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a2b031c2e9b9af485d5e3c4520f4220d74f4d222a5b8dc8c1a3ab9448ca79c57"}, + {url = "https://files.pythonhosted.org/packages/2c/e9/c79ecc36cfa34d850a01773565e0fccafd69efff07172028c3a5f758b83f/msgpack-1.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b2de4c1c0538dcb7010902a2b97f4e00fc4ddf2c8cda9749af0e594d3b7fa3d7"}, + {url = "https://files.pythonhosted.org/packages/2f/21/e488871f8e498efe14821b0c870eb95af52cfafb9b8dd41d83fad85b383b/msgpack-1.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48296af57cdb1d885843afd73c4656be5c76c0c6328db3440c9601a98f303d87"}, + {url = "https://files.pythonhosted.org/packages/33/0a/aa7b53ae17cf1dc1c352d705ab3162fc572c55048cc3177c1a88009c47fd/msgpack-1.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ab2f3331cb1b54165976a9d976cb251a83183631c88076613c6c780f0d6e45a"}, + {url = "https://files.pythonhosted.org/packages/33/52/099f0dde1283bac7bf267ab941dfa3b7c89ee701e4252973f8d3c10e68d6/msgpack-1.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:379026812e49258016dd84ad79ac8446922234d498058ae1d415f04b522d5b2d"}, + {url = "https://files.pythonhosted.org/packages/34/3c/34e94b091b3fdf941dbce5bc619e2fa5488d49fdf00944b50f5a1d6e1871/msgpack-1.0.5-cp36-cp36m-win32.whl", hash = "sha256:b5ef2f015b95f912c2fcab19c36814963b5463f1fb9049846994b007962743e9"}, + {url = "https://files.pythonhosted.org/packages/3c/e5/3d436bed11849ba05d777ed3fd1a0440170bad460335ea541dd6946047ed/msgpack-1.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:4867aa2df9e2a5fa5f76d7d5565d25ec76e84c106b55509e78c1ede0f152659a"}, + {url = "https://files.pythonhosted.org/packages/3e/80/bc7fdb75a35bf32c7c529c247dcadfd0502aac2309e207a89b0be6fe42ea/msgpack-1.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cb47c21a8a65b165ce29f2bec852790cbc04936f502966768e4aae9fa763cb7"}, + {url = "https://files.pythonhosted.org/packages/43/87/6507d56f62b958d822ae4ffe1c4507ed7d3cf37ad61114665816adcf4adc/msgpack-1.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a740fa0e4087a734455f0fc3abf5e746004c9da72fbd541e9b113013c8dc3282"}, + {url = "https://files.pythonhosted.org/packages/45/85/6b55b0cabad846d3e730226a897f878f8f63ee505668bb6c55a697b0bfb0/msgpack-1.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bf22a83f973b50f9d38e55c6aade04c41ddda19b00c4ebc558930d78eecc64ed"}, + {url = "https://files.pythonhosted.org/packages/45/e1/6408389bd2cf0c339ea317926beb64d100f60bc8d236ac59f1c1162be2e4/msgpack-1.0.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1d46dfe3832660f53b13b925d4e0fa1432b00f5f7210eb3ad3bb9a13c6204a6"}, + {url = "https://files.pythonhosted.org/packages/49/57/a28120d82f8e77622a1e1efc652389c71145f6b89b47b39814a7c6038373/msgpack-1.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57e1f3528bd95cc44684beda696f74d3aaa8a5e58c816214b9046512240ef437"}, + {url = "https://files.pythonhosted.org/packages/4b/3d/cc5eb6d69e0ecde80a78cc42f48579971ec333e509d56a4a6de1a2c40ba2/msgpack-1.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:266fa4202c0eb94d26822d9bfd7af25d1e2c088927fe8de9033d929dd5ba24c5"}, + {url = "https://files.pythonhosted.org/packages/56/50/bfcc0fad07067b6f1b09d940272ec749d5fe82570d938c2348c3ad0babf7/msgpack-1.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:821c7e677cc6acf0fd3f7ac664c98803827ae6de594a9f99563e48c5a2f27eb0"}, + {url = "https://files.pythonhosted.org/packages/59/67/f992ada3b42889f1b984e5651d63ea21ca3a92049cff6d75fe0a4a63e422/msgpack-1.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e42b9594cc3bf4d838d67d6ed62b9e59e201862a25e9a157019e171fbe672dd3"}, + {url = "https://files.pythonhosted.org/packages/60/bc/af94acdebc26b8d92d5673d20529438aa225698dc23338fb43c875c8968e/msgpack-1.0.5-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:f933bbda5a3ee63b8834179096923b094b76f0c7a73c1cfe8f07ad608c58844b"}, + {url = "https://files.pythonhosted.org/packages/62/57/170af6c6fccd2d950ea01e1faa58cae9643226fa8705baded11eca3aa8b5/msgpack-1.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c05a4a96585525916b109bb85f8cb6511db1c6f5b9d9cbcbc940dc6b4be944b"}, + {url = "https://files.pythonhosted.org/packages/62/5c/9c7fed4ca0235a2d7b8d15b4047c328976b97d2b227719e54cad1e47c244/msgpack-1.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef8108f8dedf204bb7b42994abf93882da1159728a2d4c5e82012edd92c9da9f"}, + {url = "https://files.pythonhosted.org/packages/67/f8/e3ab674f4a945308362e9342297fe6b35a89dd0f648aa325aabffa5dc210/msgpack-1.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3055b0455e45810820db1f29d900bf39466df96ddca11dfa6d074fa47054376d"}, + {url = "https://files.pythonhosted.org/packages/6b/6d/de239d77d347f1990c41b4800075a15e06f748186dd120166270dd071734/msgpack-1.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28592e20bbb1620848256ebc105fc420436af59515793ed27d5c77a217477705"}, + {url = "https://files.pythonhosted.org/packages/6b/79/0dec8f035160464ca88b221cc79691a71cf88dc25207c17f1d918b2c7bb0/msgpack-1.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c4c68d87497f66f96d50142a2b73b97972130d93677ce930718f68828b382e2"}, + {url = "https://files.pythonhosted.org/packages/6c/fa/3ca00fb1e53bcacf8c186fa6aff2d2086862b12e289bcf38227d9d40bd86/msgpack-1.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:06f5174b5f8ed0ed919da0e62cbd4ffde676a374aba4020034da05fab67b9164"}, + {url = "https://files.pythonhosted.org/packages/6c/fe/8a7747ca57074307a2e8f1de58441952a9dbdf9e8a8e5873d53a5ce0835c/msgpack-1.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5494ea30d517a3576749cad32fa27f7585c65f5f38309c88c6d137877fa28a5a"}, + {url = "https://files.pythonhosted.org/packages/72/ac/2eda5af7cd1450c52d031e48c76b280eac5bb2e588678876612f95be34ab/msgpack-1.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e57916ef1bd0fee4f21c4600e9d1da352d8816b52a599c46460e93a6e9f17086"}, + {url = "https://files.pythonhosted.org/packages/73/99/f338ce8b69e934c04e5d9187f85de1ae395882cd56e7deb48e78a1749af8/msgpack-1.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdc793c50be3f01106245a61b739328f7dccc2c648b501e237f0699fe1395b81"}, + {url = "https://files.pythonhosted.org/packages/7b/e9/b47f9e93fc381885624c40cbbbd0480b18ae11ca588162fe724d43495372/msgpack-1.0.5-cp310-cp310-win32.whl", hash = "sha256:382b2c77589331f2cb80b67cc058c00f225e19827dbc818d700f61513ab47bea"}, + {url = "https://files.pythonhosted.org/packages/7e/1c/9d0fd241a4e88e1cd2f5babea4a27ac25b1b86dbbc05fa10741e82079a93/msgpack-1.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe5c63197c55bce6385d9aee16c4d0641684628f63ace85f73571e65ad1c1e8d"}, + {url = "https://files.pythonhosted.org/packages/80/f0/c1fadb4e4a38fda19e35b1b6f887d72cc9c57778af43b53f64a8cd62e922/msgpack-1.0.5-cp38-cp38-win32.whl", hash = "sha256:1c0f7c47f0087ffda62961d425e4407961a7ffd2aa004c81b9c07d9269512f6e"}, + {url = "https://files.pythonhosted.org/packages/95/c9/560c3203c4327881c9f2de26c42dacdd9567bfe7fa43458e2a680c4bdcaf/msgpack-1.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1967f6129fc50a43bfe0951c35acbb729be89a55d849fab7686004da85103f1c"}, + {url = "https://files.pythonhosted.org/packages/9a/0b/ea8a49d24654f9e8604ea78b80a4d7b0cc31817d8fb6987001223ae7feaf/msgpack-1.0.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:36961b0568c36027c76e2ae3ca1132e35123dcec0706c4b7992683cc26c1320c"}, + {url = "https://files.pythonhosted.org/packages/9f/4a/36d936e54cf71e23ad276564465f6a54fb129e3d61520b76e13e0bb29167/msgpack-1.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:525228efd79bb831cf6830a732e2e80bc1b05436b086d4264814b4b2955b2fa9"}, + {url = "https://files.pythonhosted.org/packages/a2/e0/f3d5dd7809cf5728bb1bae683032ce50547d009be6551054815a8bf2a2da/msgpack-1.0.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4c075728a1095efd0634a7dccb06204919a2f67d1893b6aa8e00497258bf926c"}, + {url = "https://files.pythonhosted.org/packages/ab/ff/ca74e519c47139b6c08fb21db5ead2bd2eed6cb1225f9be69390cdb48182/msgpack-1.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:586d0d636f9a628ddc6a17bfd45aa5b5efaf1606d2b60fa5d87b8986326e933f"}, + {url = "https://files.pythonhosted.org/packages/b8/bc/1d5fe4732dc78ff86aaf677596da08f0ae736e60ca8ab49c1f1c7366cb1a/msgpack-1.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed40e926fa2f297e8a653c954b732f125ef97bdd4c889f243182299de27e2aa9"}, + {url = "https://files.pythonhosted.org/packages/bf/68/032e62ad44f92ba6a4ae7c45054843cdec7f0c405ecdfd166f25123b0c47/msgpack-1.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18334484eafc2b1aa47a6d42427da7fa8f2ab3d60b674120bce7a895a0a85bdd"}, + {url = "https://files.pythonhosted.org/packages/c1/57/01f2d8805160f559ec21d095fc7576a26fbaed2475af24ce4a135c380c14/msgpack-1.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e6ca5d5699bcd89ae605c150aee83b5321f2115695e741b99618f4856c50898"}, + {url = "https://files.pythonhosted.org/packages/c2/3b/70d1eaaafb451679663a72164c46fadfb93f59c90f584dcd77289f90e4c5/msgpack-1.0.5-cp36-cp36m-win_amd64.whl", hash = "sha256:288e32b47e67f7b171f86b030e527e302c91bd3f40fd9033483f2cacc37f327a"}, + {url = "https://files.pythonhosted.org/packages/c5/c1/1b591574ba71481fbf38359a8fca5108e4ad130a6dbb9b2acb3e9277d0fe/msgpack-1.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:137850656634abddfb88236008339fdaba3178f4751b28f270d2ebe77a563b6c"}, + {url = "https://files.pythonhosted.org/packages/ce/b8/89cb1809b076a4651169851aa1f98128b75cbfe14034b914c9040b13c4cf/msgpack-1.0.5-cp37-cp37m-win32.whl", hash = "sha256:cb5aaa8c17760909ec6cb15e744c3ebc2ca8918e727216e79607b7bbce9c8f77"}, + {url = "https://files.pythonhosted.org/packages/d3/32/9b7a2dba9485dd7d201e4e00638fbf86e0d535a91653889c5b4dc813efdf/msgpack-1.0.5-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:366c9a7b9057e1547f4ad51d8facad8b406bab69c7d72c0eb6f529cf76d4b85f"}, + {url = "https://files.pythonhosted.org/packages/da/46/855bdcbf004fd87b6a4451e8dcd61329439dcd9039887f71ca5085769216/msgpack-1.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:bae7de2026cbfe3782c8b78b0db9cbfc5455e079f1937cb0ab8d133496ac55e1"}, + {url = "https://files.pythonhosted.org/packages/dc/a1/eba11a0d4b764bc62966a565b470f8c6f38242723ba3057e9b5098678c30/msgpack-1.0.5.tar.gz", hash = "sha256:c075544284eadc5cddc70f4757331d99dcbc16b2bbd4849d15f8aae4cf36d31c"}, + {url = "https://files.pythonhosted.org/packages/e8/1f/be19c9c9cfdcc2ae8ee8c65dbe5f281cc1f3331f9b9523735f39b090b448/msgpack-1.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:ab31e908d8424d55601ad7075e471b7d0140d4d3dd3272daf39c5c19d936bd82"}, + {url = "https://files.pythonhosted.org/packages/e8/60/78906f564804aae23eb1102eca8b8830f1e08a649c179774c05fa7dc0aad/msgpack-1.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:17358523b85973e5f242ad74aa4712b7ee560715562554aa2134d96e7aa4cbbf"}, + {url = "https://files.pythonhosted.org/packages/e9/f1/45b73a9e97f702bcb5f51569b93990e456bc969363e55122374c22ed7d24/msgpack-1.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a61215eac016f391129a013c9e46f3ab308db5f5ec9f25811e811f96962599a8"}, + {url = "https://files.pythonhosted.org/packages/ef/13/c110d89d5079169354394dc226e6f84d818722939bc1fe3f9c25f982e903/msgpack-1.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1835c84d65f46900920b3708f5ba829fb19b1096c1800ad60bae8418652a951d"}, + {url = "https://files.pythonhosted.org/packages/f1/1f/cc3e8274934c8323f6106dae22cba8bad413166f4efb3819573de58c215c/msgpack-1.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:332360ff25469c346a1c5e47cbe2a725517919892eda5cfaffe6046656f0b7bb"}, + {url = "https://files.pythonhosted.org/packages/f2/da/770118f8d48e11cc9a2c7cb60d7d3c8016266526bd42c6ff5bd21013d099/msgpack-1.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4f8d8b3bf1ff2672567d6b5c725a1b347fe838b912772aa8ae2bf70338d5a198"}, + {url = "https://files.pythonhosted.org/packages/f5/80/ef9c31210ac580163c0de2db4fb3179c6a3f1228c18fd366280e01d9e5d2/msgpack-1.0.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f837b93669ce4336e24d08286c38761132bc7ab29782727f8557e1eb21b2080"}, +] +"multidict 6.0.4" = [ + {url = "https://files.pythonhosted.org/packages/00/bb/1cdffe9b1ab01830bc9255a64524c34b71c20a4affe5d1000b223a41698d/multidict-6.0.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d"}, + {url = "https://files.pythonhosted.org/packages/0a/a1/a0446805d76fd6ada6de501c90520c963f8b5bf1f5a7a75ad80ba076897d/multidict-6.0.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87"}, + {url = "https://files.pythonhosted.org/packages/0c/ff/342e4f8f1c83fb2bdbca067a78cb88e80a0b93aab5443c8095daa97bf94b/multidict-6.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf"}, + {url = "https://files.pythonhosted.org/packages/17/3d/081e3f2c4c6b65e6347b5a4ed465fb36041f52c2dad1ad3178ad837c4f0d/multidict-6.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11"}, + {url = "https://files.pythonhosted.org/packages/1a/3f/35c77a24a68ea1406a4d11e409e54c88eaf92afe4f7613b581d625ed812f/multidict-6.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52"}, + {url = "https://files.pythonhosted.org/packages/1a/5a/e31fc5799b6d8929da4db92cc166d9257e7f85b4d6c7245143c0dae29413/multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"}, + {url = "https://files.pythonhosted.org/packages/1b/7c/705e0f14225a748b0729d97095283b2251dbf7cada28bfe75a11b7cf2d0c/multidict-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e"}, + {url = "https://files.pythonhosted.org/packages/24/d1/56b6d5eb964161c55a8a7ad53fe4c93a694e44d04fd1405f3c1b98de5627/multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"}, + {url = "https://files.pythonhosted.org/packages/25/1f/b10a0abdfc33069b6c92935cff81b97dd7d034149b05025a92326972b371/multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"}, + {url = "https://files.pythonhosted.org/packages/27/81/2502174a4988981a33bb3458b9d5a14495b1e3e45c36ca234f75115d4723/multidict-6.0.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac"}, + {url = "https://files.pythonhosted.org/packages/27/ce/2207d548200d42c3a0b3cb11b8957f4d29f82f95977ae2cc8276a7d719e5/multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"}, + {url = "https://files.pythonhosted.org/packages/28/a3/db4511fbc4bf75a6c0afea0f009605432561ce0bd2b4fddc2047e9cb0b6b/multidict-6.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449"}, + {url = "https://files.pythonhosted.org/packages/2a/7b/6900273aec2eef33e17094407b67dca697ceeb75e3ddb86cccbdafb46e4b/multidict-6.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d"}, + {url = "https://files.pythonhosted.org/packages/2f/38/e0514ddb9b454b06fc8b29eb8b45ae1861cf1850acc2b0f01ad38b047ad3/multidict-6.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664"}, + {url = "https://files.pythonhosted.org/packages/3c/b3/1c8b525a7243c395a73d0ba35f4625333315c5261d01acc3bcde852a9548/multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"}, + {url = "https://files.pythonhosted.org/packages/3c/ee/7b419645f86d43ae393f2451bc95287aec2e7539e93af619b280aeda9b04/multidict-6.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063"}, + {url = "https://files.pythonhosted.org/packages/3d/22/35539dddb1971eb8dc88bb19d22d636eb9efe1ad7549d2f319a7951cbbe7/multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"}, + {url = "https://files.pythonhosted.org/packages/42/b6/61cb83e174e77e4e2607f60f26ff8e975eb7961e143fa01682b8c3acb201/multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"}, + {url = "https://files.pythonhosted.org/packages/46/d2/0b1e4e8ad7097dc12543571333d65580e918dd19e26109dc4b8ec13b744c/multidict-6.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95"}, + {url = "https://files.pythonhosted.org/packages/47/76/fe01957664719f8b02bd4930b2f95e4f4a3ffaca42c9f21db92a5de4156e/multidict-6.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f"}, + {url = "https://files.pythonhosted.org/packages/47/a4/69f7255bc1398caa2c1eecf4c937d3ed6ae483327f39f8b1115b578905bb/multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"}, + {url = "https://files.pythonhosted.org/packages/47/e4/745fb4cc79b439b1c1d1f441f2aa65f6250b77052d2bf4d8d8b5970ee672/multidict-6.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9"}, + {url = "https://files.pythonhosted.org/packages/4a/15/bd620f7a6eb9aa5112c4ef93e7031bcd071e0611763d8e17706ef8ba65e0/multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, + {url = "https://files.pythonhosted.org/packages/4d/1f/83656180657d0d359b12866b9af77dbb58f46cb5f454301d2c37ec97a9e1/multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"}, + {url = "https://files.pythonhosted.org/packages/56/b5/ac112889bfc68e6cf4eda1e4325789b166c51c6cd29d5633e28fb2c2f966/multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"}, + {url = "https://files.pythonhosted.org/packages/57/23/3955d3bba16dc6d75b1993d52a1b32dc93c795920e213862fab220c7d030/multidict-6.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161"}, + {url = "https://files.pythonhosted.org/packages/59/28/e50cc24c56609d11f7232606f73981620e94e3445791d9501e21c4c73a61/multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"}, + {url = "https://files.pythonhosted.org/packages/5c/4d/976b2e5fadc2b6e5e6208fb1566669460adde3f41d7622db3afa90fb2dbf/multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"}, + {url = "https://files.pythonhosted.org/packages/5d/a0/33b0b030148e9e0882d8b9f2404b8b3cc5e4718041fe6856602ccad81fa9/multidict-6.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706"}, + {url = "https://files.pythonhosted.org/packages/5f/eb/2023167c9533d62e2afcba7acb0dc98420bcf9fc27eff5a83c2bbd013b65/multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"}, + {url = "https://files.pythonhosted.org/packages/65/c4/fcbe7b0749a20d0b9adfaec89a46ceb16a187f944230fb30f62c64e6a25e/multidict-6.0.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067"}, + {url = "https://files.pythonhosted.org/packages/69/48/2750fd3ace4d778b4e1f7110db3ad637906de3496abc9c450ce726b97337/multidict-6.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1"}, + {url = "https://files.pythonhosted.org/packages/6d/9c/e5515fd09f0811045946872baeb08eb61993115d195eb8900083da21f17c/multidict-6.0.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603"}, + {url = "https://files.pythonhosted.org/packages/84/2b/2503ef1243e598d54d1516a3780858a70e9ec5de57cf03888010ee906976/multidict-6.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2"}, + {url = "https://files.pythonhosted.org/packages/85/0c/8413a4a0ad4eb4f7987546b9cd84717a14c3639efec6bc1f2f3d1d9de98d/multidict-6.0.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60"}, + {url = "https://files.pythonhosted.org/packages/89/70/6cb6d8e81d269fff05624bbd2db0a98351ed2f655c39a8f8761c362b4755/multidict-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775"}, + {url = "https://files.pythonhosted.org/packages/8f/f9/e14b11f78b937d2a5982593d5a238058679bd120979b2c5b94ea8ba125fc/multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"}, + {url = "https://files.pythonhosted.org/packages/94/e7/a1484aa7d711bc346a37dfa2f23895cc568f9f5a5f9e86498864d2e17b7e/multidict-6.0.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a"}, + {url = "https://files.pythonhosted.org/packages/96/9a/96830785d7eb3c72782fda15572ea9ed31fd67a071eab0ffad6859458e4d/multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"}, + {url = "https://files.pythonhosted.org/packages/9d/5a/34bd606569178ad8a931ea4d59cda926b046cfa4c01b0191c2e04cfd44c2/multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"}, + {url = "https://files.pythonhosted.org/packages/9f/d2/49cf9fa8a79e5aa9df5139b54842bb45b6a4cacdefc2defcc1aa10e8b1ea/multidict-6.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca"}, + {url = "https://files.pythonhosted.org/packages/a7/72/fe07bee3dd045d041f5c2e542ceaf9b685ee4775c38702e6584faacd64fd/multidict-6.0.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2"}, + {url = "https://files.pythonhosted.org/packages/ac/2d/0fa5bf39a8a595ded860adacc4188749013775c16a78472954f49baa61fa/multidict-6.0.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d"}, + {url = "https://files.pythonhosted.org/packages/b0/6d/03a5b702a0ad4d3aa4cf101acd8758ff8438fef0311bf90e7c72a80152ef/multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"}, + {url = "https://files.pythonhosted.org/packages/b0/a2/5eb04a471c99b1cc9f3c4f6aa17cbbbedf60c89f4d949ddcc4251b4ae5ab/multidict-6.0.4-cp38-cp38-win32.whl", hash = "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779"}, + {url = "https://files.pythonhosted.org/packages/b4/7a/3f0b0e533fd1b73662723cb45869f4d32df643458d78c2fa7b946be98494/multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"}, + {url = "https://files.pythonhosted.org/packages/b7/c4/24a83a598d3622be56679d233c9fa19e0334a8047b72dcfc1e1296426ca9/multidict-6.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660"}, + {url = "https://files.pythonhosted.org/packages/bb/e4/ea5687129b0cb781aba596bd08abb2aca3c8051e41aabf989c966e93af04/multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"}, + {url = "https://files.pythonhosted.org/packages/bb/ec/ea3435f339cfad0d0a5e9e533a362d230325029deea9cdba6730fcfc1e00/multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"}, + {url = "https://files.pythonhosted.org/packages/bd/50/7beeed47a950011ea0abf50541fecd67d6880719ac0e797b3dc214d6f102/multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"}, + {url = "https://files.pythonhosted.org/packages/bf/e8/9e732d21adc5321bf3adcde8e712a8af20f5cf32beaaf08267ee0dad47ca/multidict-6.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1"}, + {url = "https://files.pythonhosted.org/packages/c3/c5/b583cd706f88ef57811229b67d6c4c0fcda56bee49a913156dd401aaa729/multidict-6.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c"}, + {url = "https://files.pythonhosted.org/packages/c6/4d/7fa88fdd8f4491381ad2b2ba6e6f725823aa52e73f4541330374b26094ef/multidict-6.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d"}, + {url = "https://files.pythonhosted.org/packages/d0/21/d737fe1cac90fb89b0959194d12747024ea95d52032daef9d2ac3cf18ce0/multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"}, + {url = "https://files.pythonhosted.org/packages/d2/cf/d00992d281fb953a01685d9b2e68f66901c7dee7bcb75dad1a5ef9a879d3/multidict-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480"}, + {url = "https://files.pythonhosted.org/packages/d5/14/cb152ce2ec0874a4f6842938cd34e8e84195331d1108129b8630012b3176/multidict-6.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde"}, + {url = "https://files.pythonhosted.org/packages/d5/eb/22de4f5935f4d754b0f53d323643a1b4b7fa796e02bf3a0df7dec150269f/multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"}, + {url = "https://files.pythonhosted.org/packages/d8/a5/4ee9ed42f0eadf10a7eaa0d67e26107c0385e62cee49bacd2bd7ad934ae9/multidict-6.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78"}, + {url = "https://files.pythonhosted.org/packages/da/fe/49febc2d6b6a9bf072a4801a8ba2b5a4795f7b5444659d834c598b6b0ee1/multidict-6.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe"}, + {url = "https://files.pythonhosted.org/packages/db/7e/f007ec4ea4d6626aa4e659ae3631345cb928ff07445577914884c3355277/multidict-6.0.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176"}, + {url = "https://files.pythonhosted.org/packages/dd/bd/cce218536b377efbee70d8680a97fd282f4e1e9f7ebff95c4ea28deef87a/multidict-6.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60"}, + {url = "https://files.pythonhosted.org/packages/de/c2/2b6be6b6194064efe429b77994f3153ffd18a92b0b6422de3c702b58b150/multidict-6.0.4-cp39-cp39-win32.whl", hash = "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313"}, + {url = "https://files.pythonhosted.org/packages/e1/2b/e2b9ff85a5f973c7636d07e58ede554262a75b435eb6fe53e67ec4749953/multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"}, + {url = "https://files.pythonhosted.org/packages/e4/18/79a66879c57c37a2a721ca1aea18953f0f291ea8a8e7334fe5091a4c3111/multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"}, + {url = "https://files.pythonhosted.org/packages/e4/41/ade43649e3c35178a81827eb960a7480842fe36c51d4a16a2a68e396e0d6/multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"}, + {url = "https://files.pythonhosted.org/packages/e5/75/b629e322641d884f438fd7ca959d69dae94b25bc59035a97dd48d931515b/multidict-6.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951"}, + {url = "https://files.pythonhosted.org/packages/eb/97/05b51bd39ba10ad7ae6530ae05e050a1cac91d42dcafd40c40d388e057b4/multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"}, + {url = "https://files.pythonhosted.org/packages/f0/c1/de389de822e8442717e7fda86496a47af8a132104e1601f3419d26dff334/multidict-6.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35"}, + {url = "https://files.pythonhosted.org/packages/f1/d2/d735d40355ce41f6d1c50a5d4feef47cd4aad0e2809dd2c8cb01601f04ac/multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"}, + {url = "https://files.pythonhosted.org/packages/f1/d7/7f26fe2e790654dcc82283c17b69534c7f30213b63628e7420391d609166/multidict-6.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e"}, + {url = "https://files.pythonhosted.org/packages/f5/cf/416f84a8c7954c571881b01c839312ec81e222b3986c8baedc57f476cc1b/multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"}, + {url = "https://files.pythonhosted.org/packages/fc/54/8e025ae4e31d899e4528a570941eb7048512392b454acccf69c2dccfcb0d/multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"}, + {url = "https://files.pythonhosted.org/packages/fc/5b/0a4205a1248fb152f596a03c971c6ef1585d0c98e56b6886dc35d084e366/multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"}, + {url = "https://files.pythonhosted.org/packages/fe/0c/8469202f8f4b0e65816f91c3febc4bda7316c995b59ecdf3b15c574f7a24/multidict-6.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258"}, +] +"namesgenerator 0.3" = [ + {url = "https://files.pythonhosted.org/packages/40/09/560ba71405cdf8359b71f552155a33d5c8542a014fd2c4366f31af998cab/namesgenerator-0.3.tar.gz", hash = "sha256:50a03cc15e95edbf88a7ff86179f217f43eb2b2d6ee30fac3e9a20a54985b72e"}, +] +"nodeenv 1.8.0" = [ + {url = "https://files.pythonhosted.org/packages/1a/e6/6d2ead760a9ddb35e65740fd5a57e46aadd7b0c49861ab24f94812797a1c/nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, + {url = "https://files.pythonhosted.org/packages/48/92/8e83a37d3f4e73c157f9fcf9fb98ca39bd94701a469dc093b34dca31df65/nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, +] +"numpy 1.24.4" = [ + {url = "https://files.pythonhosted.org/packages/10/be/ae5bf4737cb79ba437879915791f6f26d92583c738d7d960ad94e5c36adf/numpy-1.24.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ffe43c74893dbf38c2b0a1f5428760a1a9c98285553c89e12d70a96a7f3a4d6"}, + {url = "https://files.pythonhosted.org/packages/11/10/943cfb579f1a02909ff96464c69893b1d25be3731b5d3652c2e0cf1281ea/numpy-1.24.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1452241c290f3e2a312c137a9999cdbf63f78864d63c79039bda65ee86943f61"}, + {url = "https://files.pythonhosted.org/packages/14/27/638aaa446f39113a3ed38b37a66243e21b38110d021bfcb940c383e120f2/numpy-1.24.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9667575fb6d13c95f1b36aca12c5ee3356bf001b714fc354eb5465ce1609e62f"}, + {url = "https://files.pythonhosted.org/packages/18/9d/e02ace5d7dfccee796c37b995c63322674daf88ae2f4a4724c5dd0afcc91/numpy-1.24.4-cp39-cp39-win32.whl", hash = "sha256:6620c0acd41dbcb368610bb2f4d83145674040025e5536954782467100aa8835"}, + {url = "https://files.pythonhosted.org/packages/22/55/3d5a7c1142e0d9329ad27cece17933b0e2ab4e54ddc5c1861fbfeb3f7693/numpy-1.24.4-cp310-cp310-win_amd64.whl", hash = "sha256:b4bea75e47d9586d31e892a7401f76e909712a0fd510f58f5337bea9572c571e"}, + {url = "https://files.pythonhosted.org/packages/22/97/dfb1a31bb46686f09e68ea6ac5c63fdee0d22d7b23b8f3f7ea07712869ef/numpy-1.24.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7215847ce88a85ce39baf9e89070cb860c98fdddacbaa6c0da3ffb31b3350bd5"}, + {url = "https://files.pythonhosted.org/packages/25/6f/2586a50ad72e8dbb1d8381f837008a0321a3516dfd7cb57fc8cf7e4bb06b/numpy-1.24.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5425b114831d1e77e4b5d812b69d11d962e104095a5b9c3b641a218abcc050e"}, + {url = "https://files.pythonhosted.org/packages/35/e2/76a11e54139654a324d107da1d98f99e7aa2a7ef97cfd7c631fba7dbde71/numpy-1.24.4-cp311-cp311-win32.whl", hash = "sha256:4979217d7de511a8d57f4b4b5b2b965f707768440c17cb70fbf254c4b225238d"}, + {url = "https://files.pythonhosted.org/packages/42/e7/4bf953c6e05df90c6d351af69966384fed8e988d0e8c54dad7103b59f3ba/numpy-1.24.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95f7ac6540e95bc440ad77f56e520da5bf877f87dca58bd095288dce8940532a"}, + {url = "https://files.pythonhosted.org/packages/5a/b3/2f9c21d799fa07053ffa151faccdceeb69beec5a010576b8991f614021f7/numpy-1.24.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79fc682a374c4a8ed08b331bef9c5f582585d1048fa6d80bc6c35bc384eee9b4"}, + {url = "https://files.pythonhosted.org/packages/63/38/6cc19d6b8bfa1d1a459daf2b3fe325453153ca7019976274b6f33d8b5663/numpy-1.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:befe2bf740fd8373cf56149a5c23a0f601e82869598d41f8e188a0e9869926f8"}, + {url = "https://files.pythonhosted.org/packages/64/5f/3f01d753e2175cfade1013eea08db99ba1ee4bdb147ebcf3623b75d12aa7/numpy-1.24.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed094d4f0c177b1b8e7aa9cba7d6ceed51c0e569a5318ac0ca9a090680a6a1b1"}, + {url = "https://files.pythonhosted.org/packages/69/65/0d47953afa0ad569d12de5f65d964321c208492064c38fe3b0b9744f8d44/numpy-1.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:692f2e0f55794943c5bfff12b3f56f99af76f902fc47487bdfe97856de51a706"}, + {url = "https://files.pythonhosted.org/packages/6b/80/6cdfb3e275d95155a34659163b83c09e3a3ff9f1456880bec6cc63d71083/numpy-1.24.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0bfb52d2169d58c1cdb8cc1f16989101639b34c7d3ce60ed70b19c63eba0b64"}, + {url = "https://files.pythonhosted.org/packages/7a/7c/d7b2a0417af6428440c0ad7cb9799073e507b1a465f827d058b826236964/numpy-1.24.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d11efb4dbecbdf22508d55e48d9c8384db795e1b7b51ea735289ff96613ff74d"}, + {url = "https://files.pythonhosted.org/packages/8f/27/91894916e50627476cff1a4e4363ab6179d01077d71b9afed41d9e1f18bf/numpy-1.24.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a86ed21e4f87050382c7bc96571755193c4c1392490744ac73d660e8f564a9"}, + {url = "https://files.pythonhosted.org/packages/98/5d/5738903efe0ecb73e51eb44feafba32bdba2081263d40c5043568ff60faf/numpy-1.24.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd80e219fd4c71fc3699fc1dadac5dcf4fd882bfc6f7ec53d30fa197b8ee22dc"}, + {url = "https://files.pythonhosted.org/packages/9a/cd/d5b0402b801c8a8b56b04c1e85c6165efab298d2f0ab741c2406516ede3a/numpy-1.24.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2541312fbf09977f3b3ad449c4e5f4bb55d0dbf79226d7724211acc905049400"}, + {url = "https://files.pythonhosted.org/packages/a4/9b/027bec52c633f6556dba6b722d9a0befb40498b9ceddd29cbe67a45a127c/numpy-1.24.4.tar.gz", hash = "sha256:80f5e3a4e498641401868df4208b74581206afbee7cf7b8329daae82676d9463"}, + {url = "https://files.pythonhosted.org/packages/a4/fd/8dff40e25e937c94257455c237b9b6bf5a30d42dd1cc11555533be099492/numpy-1.24.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:31f13e25b4e304632a4619d0e0777662c2ffea99fcae2029556b17d8ff958aef"}, + {url = "https://files.pythonhosted.org/packages/a7/4c/96cdaa34f54c05e97c1c50f39f98d608f96f0677a6589e64e53104e22904/numpy-1.24.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:222e40d0e2548690405b0b3c7b21d1169117391c2e82c378467ef9ab4c8f0da7"}, + {url = "https://files.pythonhosted.org/packages/a7/ae/f53b7b265fdc701e663fbb322a8e9d4b14d9cb7b2385f45ddfabfc4327e4/numpy-1.24.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f"}, + {url = "https://files.pythonhosted.org/packages/a9/cc/5ed2280a27e5dab12994c884f1f4d8c3bd4d885d02ae9e52a9d213a6a5e2/numpy-1.24.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f136bab9c2cfd8da131132c2cf6cc27331dd6fae65f95f69dcd4ae3c3639c810"}, + {url = "https://files.pythonhosted.org/packages/c0/64/908c1087be6285f40e4b3e79454552a701664a079321cff519d8c7051d06/numpy-1.24.4-cp310-cp310-win32.whl", hash = "sha256:4c21decb6ea94057331e111a5bed9a79d335658c27ce2adb580fb4d54f2ad9bc"}, + {url = "https://files.pythonhosted.org/packages/c0/bc/77635c657a3668cf652806210b8662e1aff84b818a55ba88257abf6637a8/numpy-1.24.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2926dac25b313635e4d6cf4dc4e51c8c0ebfed60b801c799ffc4c32bf3d1254"}, + {url = "https://files.pythonhosted.org/packages/d1/57/8d328f0b91c733aa9aa7ee540dbc49b58796c862b4fbcb1146c701e888da/numpy-1.24.4-cp38-cp38-win32.whl", hash = "sha256:4602244f345453db537be5314d3983dbf5834a9701b7723ec28923e2889e0bb2"}, + {url = "https://files.pythonhosted.org/packages/d8/ec/ebef2f7d7c28503f958f0f8b992e7ce606fb74f9e891199329d5f5f87404/numpy-1.24.4-cp311-cp311-win_amd64.whl", hash = "sha256:b7b1fc9864d7d39e28f41d089bfd6353cb5f27ecd9905348c24187a768c79694"}, + {url = "https://files.pythonhosted.org/packages/fc/dd/9106005eb477d022b60b3817ed5937a43dad8fd1f20b0610ea8a32fcb407/numpy-1.24.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e98f220aa76ca2a977fe435f5b04d7b3470c0a2e6312907b37ba6068f26787f2"}, +] +"opentelemetry-api 1.18.0" = [ + {url = "https://files.pythonhosted.org/packages/62/a1/7b776ff98db75d43c2f5b1d320af0a61ff6d1b2b37b18cf2618d167354a9/opentelemetry_api-1.18.0-py3-none-any.whl", hash = "sha256:d05bcc94ec239fd76fd90d784c5e3ad081a8a1ac2ffc8a2c83a49ace052d1492"}, + {url = "https://files.pythonhosted.org/packages/f5/c8/59c36fb0c45491b7fd3ca15f96c05e7a3cd32b5a6be19ca801a5d556b701/opentelemetry_api-1.18.0.tar.gz", hash = "sha256:2bbf29739fcef268c419e3bf1735566c2e7f81026c14bcc78b62a0b97f8ecf2f"}, +] +"opentelemetry-instrumentation 0.38b0" = [ + {url = "https://files.pythonhosted.org/packages/01/aa/edc30bd716369543ef3fc2f08fa603b1c15ca6b87bed936a0ad1bc2d34e4/opentelemetry_instrumentation-0.38b0.tar.gz", hash = "sha256:3dbe93248eec7652d5725d3c6d2f9dd048bb8fda6b0505aadbc99e51638d833c"}, + {url = "https://files.pythonhosted.org/packages/b0/10/1948d4056b17a214e77f62b225d768916fd28ffc523673b3988259dd11d5/opentelemetry_instrumentation-0.38b0-py3-none-any.whl", hash = "sha256:48eed87e5db9d2cddd57a8ea359bd15318560c0ffdd80d90a5fc65816e15b7f4"}, +] +"opentelemetry-instrumentation-asgi 0.38b0" = [ + {url = "https://files.pythonhosted.org/packages/1a/4b/8038bcb42aba6b6c71d54026ad9b29fdc1bb7ee6d198571dcebdb1893d44/opentelemetry_instrumentation_asgi-0.38b0-py3-none-any.whl", hash = "sha256:c5bba11505008a3cd1b2c42b72f85f3f4f5af50ab931eddd0b01bde376dc5971"}, + {url = "https://files.pythonhosted.org/packages/27/83/d0779230c4d45621231a24c0e76ce91842c191e87ac5130c1f552a5a3bff/opentelemetry_instrumentation_asgi-0.38b0.tar.gz", hash = "sha256:32d1034c253de6048d0d0166b304f9125267ca9329e374202ebe011a206eba53"}, +] +"opentelemetry-instrumentation-fastapi 0.38b0" = [ + {url = "https://files.pythonhosted.org/packages/0e/8c/6b5f411ce2d8b64b0aaf8bf288fa523f10a325ed1efbadc5f1e14774edf7/opentelemetry_instrumentation_fastapi-0.38b0-py3-none-any.whl", hash = "sha256:91139586732e437b1c3d5cf838dc5be910bce27b4b679612112be03fcc4fa2aa"}, + {url = "https://files.pythonhosted.org/packages/28/60/d1769efb924d9f5bbad23eaea9ef7c2f4985c01cdcdeeb42156ddbfe19c6/opentelemetry_instrumentation_fastapi-0.38b0.tar.gz", hash = "sha256:8946fd414084b305ad67556a1907e2d4a497924d023effc5ea3b4b1b0c55b256"}, +] +"opentelemetry-semantic-conventions 0.38b0" = [ + {url = "https://files.pythonhosted.org/packages/0b/b0/c8bdd02ce280f41e9d0568a37ead4c0153d5200f887a5f6858839daa5bb6/opentelemetry_semantic_conventions-0.38b0-py3-none-any.whl", hash = "sha256:b0ba36e8b70bfaab16ee5a553d809309cc11ff58aec3d2550d451e79d45243a7"}, + {url = "https://files.pythonhosted.org/packages/cb/67/2e34aa3dde0558017ec071591d04aeb99f29dadad9f72ff59e490011ccb6/opentelemetry_semantic_conventions-0.38b0.tar.gz", hash = "sha256:37f09e47dd5fc316658bf9ee9f37f9389b21e708faffa4a65d6a3de484d22309"}, +] +"opentelemetry-util-http 0.38b0" = [ + {url = "https://files.pythonhosted.org/packages/ce/3f/d2dd64948895f1a32c8e57e9c433079d7d6fa9bb94b93a90e38ca46053a1/opentelemetry_util_http-0.38b0.tar.gz", hash = "sha256:85eb032b6129c4d7620583acf574e99fe2e73c33d60e256b54af436f76ceb5ae"}, + {url = "https://files.pythonhosted.org/packages/f0/a3/54ea1d852bbc5348743017b5ef09f9119880bda147021d3d5e651d51cc04/opentelemetry_util_http-0.38b0-py3-none-any.whl", hash = "sha256:8e5f0451eeb5307b2c628dd799886adc5e113fb13a7207c29c672e8d168eabd8"}, +] +"packaging 23.1" = [ + {url = "https://files.pythonhosted.org/packages/ab/c3/57f0601a2d4fe15de7a553c00adbc901425661bf048f2a22dfc500caf121/packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, + {url = "https://files.pythonhosted.org/packages/b9/6c/7c6658d258d7971c5eb0d9b69fa9265879ec9a9158031206d47800ae2213/packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, +] +"pandas 2.0.3" = [ + {url = "https://files.pythonhosted.org/packages/26/7d/d8aa0a2c4f3f5f8ea59fb946c8eafe8f508090ca73e2b08a9af853c1103e/pandas-2.0.3-cp39-cp39-win32.whl", hash = "sha256:04dbdbaf2e4d46ca8da896e1805bc04eb85caa9a82e259e8eed00254d5e0c682"}, + {url = "https://files.pythonhosted.org/packages/3c/b2/0d4a5729ce1ce11630c4fc5d5522a33b967b3ca146c210f58efde7c40e99/pandas-2.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c7c9f27a4185304c7caf96dc7d91bc60bc162221152de697c98eb0b2648dd8"}, + {url = "https://files.pythonhosted.org/packages/4a/f6/f620ca62365d83e663a255a41b08d2fc2eaf304e0b8b21bb6d62a7390fe3/pandas-2.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f167beed68918d62bffb6ec64f2e1d8a7d297a038f86d4aed056b9493fca407f"}, + {url = "https://files.pythonhosted.org/packages/53/c3/f8e87361f7fdf42012def602bfa2a593423c729f5cb7c97aed7f51be66ac/pandas-2.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:32fca2ee1b0d93dd71d979726b12b61faa06aeb93cf77468776287f41ff8fdc5"}, + {url = "https://files.pythonhosted.org/packages/6c/1c/689c9d99bc4e5d366a5fd871f0bcdee98a6581e240f96b78d2d08f103774/pandas-2.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:81af086f4543c9d8bb128328b5d32e9986e0c84d3ee673a2ac6fb57fd14f755e"}, + {url = "https://files.pythonhosted.org/packages/78/a8/07dd10f90ca915ed914853cd57f79bfc22e1ef4384ab56cb4336d2fc1f2a/pandas-2.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9e4da0d45e7f34c069fe4d522359df7d23badf83abc1d1cef398895822d11061"}, + {url = "https://files.pythonhosted.org/packages/8f/bb/aea1fbeed5b474cb8634364718abe9030d7cc7a30bf51f40bd494bbc89a2/pandas-2.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37673e3bdf1551b95bf5d4ce372b37770f9529743d2498032439371fc7b7eb26"}, + {url = "https://files.pythonhosted.org/packages/94/71/3a0c25433c54bb29b48e3155b959ac78f4c4f2f06f94d8318aac612cb80f/pandas-2.0.3-cp310-cp310-win32.whl", hash = "sha256:3ef285093b4fe5058eefd756100a367f27029913760773c8bf1d2d8bebe5d210"}, + {url = "https://files.pythonhosted.org/packages/9a/f2/0ad053856debbe90c83de1b4f05915f85fd2146f20faf9daa3b320d36df3/pandas-2.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:1168574b036cd8b93abc746171c9b4f1b83467438a5e45909fed645cf8692dbc"}, + {url = "https://files.pythonhosted.org/packages/9e/0d/91a9fd2c202f2b1d97a38ab591890f86480ecbb596cbc56d035f6f23fdcc/pandas-2.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ec591c48e29226bcbb316e0c1e9423622bc7a4eaf1ef7c3c9fa1a3981f89641"}, + {url = "https://files.pythonhosted.org/packages/9e/71/756a1be6bee0209d8c0d8c5e3b9fc72c00373f384a4017095ec404aec3ad/pandas-2.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:6a21ab5c89dcbd57f78d0ae16630b090eec626360085a4148693def5452d8a6b"}, + {url = "https://files.pythonhosted.org/packages/a7/87/828d50c81ce0f434163bf70b925a0eec6076808e0bca312a79322b141f66/pandas-2.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:258d3624b3ae734490e4d63c430256e716f488c4fcb7c8e9bde2d3aa46c29089"}, + {url = "https://files.pythonhosted.org/packages/b1/a7/824332581e258b5aa4f3763ecb2a797e5f9a54269044ba2e50ac19936b32/pandas-2.0.3.tar.gz", hash = "sha256:c02f372a88e0d17f36d3093a644c73cfc1788e876a7c4bcb4020a77512e2043c"}, + {url = "https://files.pythonhosted.org/packages/b3/92/a5e5133421b49e901a12e02a6a7ef3a0130e10d13db8cb657fdd0cba3b90/pandas-2.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b084b91d8d66ab19f5bb3256cbd5ea661848338301940e17f4492b2ce0801fe8"}, + {url = "https://files.pythonhosted.org/packages/c2/59/cb4234bc9b968c57e81861b306b10cd8170272c57b098b724d3de5eda124/pandas-2.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce0c6f76a0f1ba361551f3e6dceaff06bde7514a374aa43e33b588ec10420183"}, + {url = "https://files.pythonhosted.org/packages/c3/6c/ea362eef61f05553aaf1a24b3e96b2d0603f5dc71a3bd35688a24ed88843/pandas-2.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:69d7f3884c95da3a31ef82b7618af5710dba95bb885ffab339aad925c3e8ce78"}, + {url = "https://files.pythonhosted.org/packages/cc/b8/4d082f41c27c95bf90485d1447b647cc7e5680fea75e315669dc6e4cb398/pandas-2.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1994c789bf12a7c5098277fb43836ce090f1073858c10f9220998ac74f37c69b"}, + {url = "https://files.pythonhosted.org/packages/d0/28/88b81881c056376254618fad622a5e94b5126db8c61157ea1910cd1c040a/pandas-2.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9cd88488cceb7635aebb84809d087468eb33551097d600c6dad13602029c2df"}, + {url = "https://files.pythonhosted.org/packages/d6/90/e7d387f1a416b14e59290baa7a454a90d719baebbf77433ff1bdcc727800/pandas-2.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9cb1e14fdb546396b7e1b923ffaeeac24e4cedd14266c3497216dd4448e4f2d"}, + {url = "https://files.pythonhosted.org/packages/e3/59/35a2892bf09ded9c1bf3804461efe772836a5261ef5dfb4e264ce813ff99/pandas-2.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba619e410a21d8c387a1ea6e8a0e49bb42216474436245718d7f2e88a2f8d7c0"}, + {url = "https://files.pythonhosted.org/packages/e4/a5/212b9039e25bf8ebb97e417a96660e3dc925dacd3f8653d531b8f7fd9be4/pandas-2.0.3-cp311-cp311-win32.whl", hash = "sha256:694888a81198786f0e164ee3a581df7d505024fbb1f15202fc7db88a71d84ebd"}, + {url = "https://files.pythonhosted.org/packages/ea/ae/26a2eda7fa581347d69e51f93892493b2074ef3352ac71033c9f32c52389/pandas-2.0.3-cp38-cp38-win32.whl", hash = "sha256:f3421a7afb1a43f7e38e82e844e2bca9a6d793d66c1a7f9f0ff39a795bbc5e02"}, + {url = "https://files.pythonhosted.org/packages/ed/30/b97456e7063edac0e5a405128065f0cd2033adfe3716fb2256c186bd41d0/pandas-2.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:9ee1a69328d5c36c98d8e74db06f4ad518a1840e8ccb94a4ba86920986bb617e"}, + {url = "https://files.pythonhosted.org/packages/f8/7f/5b047effafbdd34e52c9e2d7e44f729a0655efafb22198c45cf692cdc157/pandas-2.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eae3dc34fa1aa7772dd3fc60270d13ced7346fcbcfee017d3132ec625e23bb0"}, + {url = "https://files.pythonhosted.org/packages/f8/c7/cfef920b7b457dff6928e824896cb82367650ea127d048ee0b820026db4f/pandas-2.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5247fb1ba347c1261cbbf0fcfba4a3121fbb4029d95d9ef4dc45406620b25c8b"}, +] +"platformdirs 3.8.0" = [ + {url = "https://files.pythonhosted.org/packages/cb/10/e5478cc0c3ee5563f91ab7b9da15d16e21f3737b6286ed3fd9a8fb1a99dd/platformdirs-3.8.0.tar.gz", hash = "sha256:b0cabcb11063d21a0b261d557acb0a9d2126350e63b70cdf7db6347baea456dc"}, + {url = "https://files.pythonhosted.org/packages/e7/61/7fde5beff25a0dae6c2056203696169bd29188b6cedefff8ba6e7b54417b/platformdirs-3.8.0-py3-none-any.whl", hash = "sha256:ca9ed98ce73076ba72e092b23d3c93ea6c4e186b3f1c3dad6edd98ff6ffcca2e"}, +] +"pluggy 1.2.0" = [ + {url = "https://files.pythonhosted.org/packages/51/32/4a79112b8b87b21450b066e102d6608907f4c885ed7b04c3fdb085d4d6ae/pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, + {url = "https://files.pythonhosted.org/packages/8a/42/8f2833655a29c4e9cb52ee8a2be04ceac61bcff4a680fb338cbd3d1e322d/pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, +] +"pre-commit 3.3.3" = [ + {url = "https://files.pythonhosted.org/packages/35/0e/564c71fe3cdf59a4acaaccaea354d066e5d9044eba564dac070bb2075432/pre_commit-3.3.3.tar.gz", hash = "sha256:a2256f489cd913d575c145132ae196fe335da32d91a8294b7afe6622335dd023"}, + {url = "https://files.pythonhosted.org/packages/e3/b7/1d145c985d8be9729672a45b8b8113030ad60dff45dec592efc4e5f5897a/pre_commit-3.3.3-py2.py3-none-any.whl", hash = "sha256:10badb65d6a38caff29703362271d7dca483d01da88f9d7e05d0b97171c136cb"}, +] +"prompt-toolkit 3.0.39" = [ + {url = "https://files.pythonhosted.org/packages/9a/02/76cadde6135986dc1e82e2928f35ebeb5a1af805e2527fe466285593a2ba/prompt_toolkit-3.0.39.tar.gz", hash = "sha256:04505ade687dc26dc4284b1ad19a83be2f2afe83e7a828ace0c72f3a1df72aac"}, + {url = "https://files.pythonhosted.org/packages/a9/b4/ba77c84edf499877317225d7b7bc047a81f7c2eed9628eeb6bab0ac2e6c9/prompt_toolkit-3.0.39-py3-none-any.whl", hash = "sha256:9dffbe1d8acf91e3de75f3b544e4842382fc06c6babe903ac9acb74dc6e08d88"}, +] +"pydantic 1.10.8" = [ + {url = "https://files.pythonhosted.org/packages/05/43/e39c6bf32695f2d568ebb2f6a3dd843c8e2edb57c77a4a911d517b5675b2/pydantic-1.10.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:35db5301b82e8661fa9c505c800d0990bc14e9f36f98932bb1d248c0ac5cada5"}, + {url = "https://files.pythonhosted.org/packages/0b/39/afbca0ea8e766ccf04f224520b95ca29d5a18b680c0780609a2c39293f8b/pydantic-1.10.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1243d28e9b05003a89d72e7915fdb26ffd1d39bdd39b00b7dbe4afae4b557f9d"}, + {url = "https://files.pythonhosted.org/packages/13/dc/54ceed364e733f81596a4f113de2098221b3d39b4eb7abbffa64e681f243/pydantic-1.10.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:666bdf6066bf6dbc107b30d034615d2627e2121506c555f73f90b54a463d1f33"}, + {url = "https://files.pythonhosted.org/packages/15/27/c35f6fefc782aebcff9991b28728f3855b1253ff757e6dee8e3ac3815cd0/pydantic-1.10.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:191ba419b605f897ede9892f6c56fb182f40a15d309ef0142212200a10af4c18"}, + {url = "https://files.pythonhosted.org/packages/23/65/2aa13873e9e0084ecaec00fbe6c6096b65e1ab99ba66bdbf7e4e7c4cc915/pydantic-1.10.8.tar.gz", hash = "sha256:1410275520dfa70effadf4c21811d755e7ef9bb1f1d077a21958153a92c8d9ca"}, + {url = "https://files.pythonhosted.org/packages/2d/a2/e3ac01dd929485a6280518d280d8cf313558c878c91d86b3a95b1702938b/pydantic-1.10.8-cp310-cp310-win_amd64.whl", hash = "sha256:ab523c31e22943713d80d8d342d23b6f6ac4b792a1e54064a8d0cf78fd64e800"}, + {url = "https://files.pythonhosted.org/packages/36/60/b24bd42bdd385fee681cc1231ef1d423566d4e33e867df4d2bd08b531466/pydantic-1.10.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f2e754d5566f050954727c77f094e01793bcb5725b663bf628fa6743a5a9108"}, + {url = "https://files.pythonhosted.org/packages/56/b5/903cd28ab9a3bf8cbfbe0a6a87d9463ceac7610193cd1d72bb1bdb276d01/pydantic-1.10.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f90c1e29f447557e9e26afb1c4dbf8768a10cc676e3781b6a577841ade126b85"}, + {url = "https://files.pythonhosted.org/packages/57/ce/b3de85c397a03f1c8dadebe33fa81b195b6090c840a0333769fba00693fd/pydantic-1.10.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1952526ba40b220b912cdc43c1c32bcf4a58e3f192fa313ee665916b26befb68"}, + {url = "https://files.pythonhosted.org/packages/59/ab/1de0d5386a464ef527338d320216a2f41de416e204780e00baa0e5e3b807/pydantic-1.10.8-cp38-cp38-win_amd64.whl", hash = "sha256:6a82d6cda82258efca32b40040228ecf43a548671cb174a1e81477195ed3ed56"}, + {url = "https://files.pythonhosted.org/packages/6b/15/3504de0fcb90336680916ea3fde845d01fa846c95ab4342c28d985c0d29d/pydantic-1.10.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93e766b4a8226e0708ef243e843105bf124e21331694367f95f4e3b4a92bbb3f"}, + {url = "https://files.pythonhosted.org/packages/6c/32/0755046e707a468fe276fd40df11d492a72d1cbcfa344091e3a46120131c/pydantic-1.10.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0ab53b609c11dfc0c060d94335993cc2b95b2150e25583bec37a49b2d6c6c3f"}, + {url = "https://files.pythonhosted.org/packages/6c/f9/5edecae1914fc7dc6a566809a5242c97d63acfb92253b0bb885d890eb953/pydantic-1.10.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42aa0c4b5c3025483240a25b09f3c09a189481ddda2ea3a831a9d25f444e03c1"}, + {url = "https://files.pythonhosted.org/packages/6f/4d/7647a5f98fbcbb9bdb1e5a77eca931a1f83255c9aa14448794a0596b5a42/pydantic-1.10.8-cp37-cp37m-win_amd64.whl", hash = "sha256:16f8c3e33af1e9bb16c7a91fc7d5fa9fe27298e9f299cff6cb744d89d573d62c"}, + {url = "https://files.pythonhosted.org/packages/77/ea/2b96534811f867bb53edaf2a3ca5037d8bcbceb05d5930bac5caa1fba573/pydantic-1.10.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1ced8375969673929809d7f36ad322934c35de4af3b5e5b09ec967c21f9f7887"}, + {url = "https://files.pythonhosted.org/packages/7a/ba/439e2bc693d3f464946159a76724efc570cef9f4e27303fa3b360b2f3ef7/pydantic-1.10.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ceb6a23bf1ba4b837d0cfe378329ad3f351b5897c8d4914ce95b85fba96da5a1"}, + {url = "https://files.pythonhosted.org/packages/98/20/52707fc7dc91b6e580dbd30c4a6b88e426f61af9f2547bb52e880f09e67d/pydantic-1.10.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12f7b0bf8553e310e530e9f3a2f5734c68699f42218bf3568ef49cd9b0e44df4"}, + {url = "https://files.pythonhosted.org/packages/a7/27/80672dfb14e47293cca421580141ec923a1e5fe7283f775079e006b0be28/pydantic-1.10.8-cp311-cp311-win_amd64.whl", hash = "sha256:d532bf00f381bd6bc62cabc7d1372096b75a33bc197a312b03f5838b4fb84edd"}, + {url = "https://files.pythonhosted.org/packages/b2/43/8eca9ebbfd861209365c5b9f982b113275eccd892e53ab7bde60a21439e8/pydantic-1.10.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:34d327c81e68a1ecb52fe9c8d50c8a9b3e90d3c8ad991bfc8f953fb477d42fb4"}, + {url = "https://files.pythonhosted.org/packages/b8/45/538d65960c489a1aa9cbf1f54d4b911e1e838d557d2d2ccd1b6c8fa10f3b/pydantic-1.10.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:17aef11cc1b997f9d574b91909fed40761e13fac438d72b81f902226a69dac01"}, + {url = "https://files.pythonhosted.org/packages/c1/37/d136df986c0a2d20f940d360fe472ae410fba46f55a73e872fd3168f4289/pydantic-1.10.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0c6fafa0965b539d7aab0a673a046466d23b86e4b0e8019d25fd53f4df62c277"}, + {url = "https://files.pythonhosted.org/packages/c4/f3/c5dc9f49783a6407487d20c9a32bca878ebf2df155b8d2858838d79b6d46/pydantic-1.10.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bb14388ec45a7a0dc429e87def6396f9e73c8c77818c927b6a60706603d5f2ea"}, + {url = "https://files.pythonhosted.org/packages/c5/58/71d48d4154e5845192f4ccc6c6ebcf6fa5286fa3bcb3c595aa18a5bf599d/pydantic-1.10.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e82d4566fcd527eae8b244fa952d99f2ca3172b7e97add0b43e2d97ee77f81ab"}, + {url = "https://files.pythonhosted.org/packages/ca/5b/8b2c49589c826bf2796fc523d77d46fed2e82585c87c812f289ce244c88b/pydantic-1.10.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e4148e635994d57d834be1182a44bdb07dd867fa3c2d1b37002000646cc5459"}, + {url = "https://files.pythonhosted.org/packages/cc/a4/354a73bb8a06df0df0bc74b5fbf3b9510ed4900185f86a00861dcfbe60c7/pydantic-1.10.8-py3-none-any.whl", hash = "sha256:7456eb22ed9aaa24ff3e7b4757da20d9e5ce2a81018c1b3ebd81a0b88a18f3b2"}, + {url = "https://files.pythonhosted.org/packages/d8/7b/ca035af1833c6d047eeb328438a2ae402d03929be2055cd66294542a814d/pydantic-1.10.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:052d8654cb65174d6f9490cc9b9a200083a82cf5c3c5d3985db765757eb3b375"}, + {url = "https://files.pythonhosted.org/packages/dc/92/3a09ec18592ca6fc96223b42ad20c8711847a8d2e1800779f9206c2fa6a2/pydantic-1.10.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df7800cb1984d8f6e249351139667a8c50a379009271ee6236138a22a0c0f319"}, + {url = "https://files.pythonhosted.org/packages/e2/21/e6f68631ec2f0470e28722d1ca352bac4f25aef6eb18b8e65ba3cd9ae8a2/pydantic-1.10.8-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b1f6cb446470b7ddf86c2e57cd119a24959af2b01e552f60705910663af09a4"}, + {url = "https://files.pythonhosted.org/packages/e6/dd/6f9ef794df128746581bd5886c6382a19f1729ff39f3d65e66e3b6751c7a/pydantic-1.10.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c33b60054b2136aef8cf190cd4c52a3daa20b2263917c49adad20eaf381e823b"}, + {url = "https://files.pythonhosted.org/packages/e7/a3/329824b0e46edcb2c51f0fa73678f24aba083289697a0db3036f4f30e1ed/pydantic-1.10.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e59417ba8a17265e632af99cc5f35ec309de5980c440c255ab1ca3ae96a3e0e"}, + {url = "https://files.pythonhosted.org/packages/e8/b3/b748afd5f4fd8f640e08cf4828fa5c9da865353eade18b9c789726b1a0ce/pydantic-1.10.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9613fadad06b4f3bc5db2653ce2f22e0de84a7c6c293909b48f6ed37b83c61f"}, + {url = "https://files.pythonhosted.org/packages/e9/17/a840d0631a288a4400e23a9ec96d131bd07be820fe2c1d070995de6dfb61/pydantic-1.10.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:93e6bcfccbd831894a6a434b0aeb1947f9e70b7468f274154d03d71fabb1d7c6"}, + {url = "https://files.pythonhosted.org/packages/fa/3b/279a13153350b688fb5eb557acf980059a21ffede20d9b6fbc5368778bf4/pydantic-1.10.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:84d80219c3f8d4cad44575e18404099c76851bc924ce5ab1c4c8bb5e2a2227d0"}, + {url = "https://files.pythonhosted.org/packages/fb/46/723587abb4aecf82edcfaa213a827d61854ebcbf76b4818cbf59c8868f4e/pydantic-1.10.8-cp39-cp39-win_amd64.whl", hash = "sha256:66a703d1983c675a6e0fed8953b0971c44dba48a929a2000a493c3772eb61a5a"}, + {url = "https://files.pythonhosted.org/packages/fe/26/66c9ac1e21a3bda4f5c10785b3ff199e12e2d1e984780a8bfa796bb4e2f0/pydantic-1.10.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7d5b8641c24886d764a74ec541d2fc2c7fb19f6da2a4001e6d580ba4a38f7878"}, + {url = "https://files.pythonhosted.org/packages/ff/b4/b56bd5f591969df63a260555a891bf953536eefcbe66b711b80f86acc3a4/pydantic-1.10.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:88f195f582851e8db960b4a94c3e3ad25692c1c1539e2552f3df7a9e972ef60e"}, +] +"pygments 2.15.1" = [ + {url = "https://files.pythonhosted.org/packages/34/a7/37c8d68532ba71549db4212cb036dbd6161b40e463aba336770e80c72f84/Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"}, + {url = "https://files.pythonhosted.org/packages/89/6b/2114e54b290824197006e41be3f9bbe1a26e9c39d1f5fa20a6d62945a0b3/Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"}, +] +"pylint 2.17.4" = [ + {url = "https://files.pythonhosted.org/packages/04/4c/3f7d42a1378c40813772bc5f25184144da09f00ffbe3f60ae985ffa7e10f/pylint-2.17.4-py3-none-any.whl", hash = "sha256:7a1145fb08c251bdb5cca11739722ce64a63db479283d10ce718b2460e54123c"}, + {url = "https://files.pythonhosted.org/packages/7e/d4/aba77d10841710fea016422f419dfe501dee05fa0fc3898dc048f7bf3f60/pylint-2.17.4.tar.gz", hash = "sha256:5dcf1d9e19f41f38e4e85d10f511e5b9c35e1aa74251bf95cdd8cb23584e2db1"}, +] +"pytest 7.4.0" = [ + {url = "https://files.pythonhosted.org/packages/33/b2/741130cbcf2bbfa852ed95a60dc311c9e232c7ed25bac3d9b8880a8df4ae/pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, + {url = "https://files.pythonhosted.org/packages/a7/f3/dadfbdbf6b6c8b5bd02adb1e08bc9fbb45ba51c68b0893fa536378cdf485/pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, +] +"pytest-asyncio 0.21.0" = [ + {url = "https://files.pythonhosted.org/packages/66/73/817ddb37c627338ecbb96486c03fe69a19bef72de1b6bd641aa06fed13f4/pytest_asyncio-0.21.0-py3-none-any.whl", hash = "sha256:f2b3366b7cd501a4056858bd39349d5af19742aed2d81660b7998b6341c7eb9c"}, + {url = "https://files.pythonhosted.org/packages/85/c7/9db0c6215f12f26b590c24acc96d048e03989315f198454540dff95109cd/pytest-asyncio-0.21.0.tar.gz", hash = "sha256:2b38a496aef56f56b0e87557ec313e11e1ab9276fc3863f6a7be0f1d0e415e1b"}, +] +"pytest-cov 4.1.0" = [ + {url = "https://files.pythonhosted.org/packages/7a/15/da3df99fd551507694a9b01f512a2f6cf1254f33601605843c3775f39460/pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, + {url = "https://files.pythonhosted.org/packages/a7/4b/8b78d126e275efa2379b1c2e09dc52cf70df16fc3b90613ef82531499d73/pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, +] +"pytest-integration 0.2.3" = [ + {url = "https://files.pythonhosted.org/packages/1b/41/9b393be6252635e4d39c3e62805018c42bfcc486b42246b582b755ff9ad3/pytest_integration-0.2.3-py3-none-any.whl", hash = "sha256:7f59ed1fa1cc8cb240f9495b68bc02c0421cce48589f78e49b7b842231604b12"}, + {url = "https://files.pythonhosted.org/packages/35/e0/c823048dc0866f2e0fa2e4a34cd6ec290697b238b7672b30cb07c65e59cc/pytest_integration-0.2.3.tar.gz", hash = "sha256:b00988a5de8a6826af82d4c7a3485b43fbf32c11235e9f4a8b7225eef5fbcf65"}, +] +"pytest-mock 3.11.1" = [ + {url = "https://files.pythonhosted.org/packages/d8/2d/b3a811ec4fa24190a9ec5013e23c89421a7916167c6240c31fdc445f850c/pytest-mock-3.11.1.tar.gz", hash = "sha256:7f6b125602ac6d743e523ae0bfa71e1a697a2f5534064528c6ff84c2f7c2fc7f"}, + {url = "https://files.pythonhosted.org/packages/da/85/80ae98e019a429445bfb74e153d4cb47c3695e3e908515e95e95c18237e5/pytest_mock-3.11.1-py3-none-any.whl", hash = "sha256:21c279fff83d70763b05f8874cc9cfb3fcacd6d354247a976f9529d19f9acf39"}, +] +"python-dateutil 2.8.2" = [ + {url = "https://files.pythonhosted.org/packages/36/7a/87837f39d0296e723bb9b62bbb257d0355c7f6128853c78955f57342a56d/python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, + {url = "https://files.pythonhosted.org/packages/4c/c4/13b4776ea2d76c115c1d1b84579f3764ee6d57204f6be27119f13a61d0a9/python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, +] +"python-dotenv 0.21.1" = [ + {url = "https://files.pythonhosted.org/packages/64/62/f19d1e9023aacb47241de3ab5a5d5fedf32c78a71a9e365bb2153378c141/python_dotenv-0.21.1-py3-none-any.whl", hash = "sha256:41e12e0318bebc859fcc4d97d4db8d20ad21721a6aa5047dd59f090391cb549a"}, + {url = "https://files.pythonhosted.org/packages/f5/d7/d548e0d5a68b328a8d69af833a861be415a17cb15ce3d8f0cd850073d2e1/python-dotenv-0.21.1.tar.gz", hash = "sha256:1c93de8f636cde3ce377292818d0e440b6e45a82f215c3744979151fa8151c49"}, +] +"pytz 2023.3" = [ + {url = "https://files.pythonhosted.org/packages/5e/32/12032aa8c673ee16707a9b6cdda2b09c0089131f35af55d443b6a9c69c1d/pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, + {url = "https://files.pythonhosted.org/packages/7f/99/ad6bd37e748257dd70d6f85d916cafe79c0b0f5e2e95b11f7fbc82bf3110/pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"}, +] +"pyyaml 6.0" = [ + {url = "https://files.pythonhosted.org/packages/02/25/6ba9f6bb50a3d4fbe22c1a02554dc670682a07c8701d1716d19ddea2c940/PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {url = "https://files.pythonhosted.org/packages/08/f4/ffa743f860f34a5e8c60abaaa686f82c9ac7a2b50e5a1c3b1eb564d59159/PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {url = "https://files.pythonhosted.org/packages/0f/93/5f81d1925ce3b531f5ff215376445ec220887cd1c9a8bde23759554dbdfd/PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {url = "https://files.pythonhosted.org/packages/12/fc/a4d5a7554e0067677823f7265cb3ae22aed8a238560b5133b58cda252dad/PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {url = "https://files.pythonhosted.org/packages/21/67/b42191239c5650c9e419c4a08a7a022bbf1abf55b0391c380a72c3af5462/PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {url = "https://files.pythonhosted.org/packages/2e/b3/13dfd4eeb5e4b2d686b6d1822b40702e991bf3a4194ca5cbcce8d43749db/PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {url = "https://files.pythonhosted.org/packages/36/2b/61d51a2c4f25ef062ae3f74576b01638bebad5e045f747ff12643df63844/PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, + {url = "https://files.pythonhosted.org/packages/44/e5/4fea13230bcebf24b28c0efd774a2dd65a0937a2d39e94a4503438b078ed/PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {url = "https://files.pythonhosted.org/packages/4d/7d/c2ab8da648cd2b937de11fb35649b127adab4851cbeaf5fd9b60a2dab0f7/PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {url = "https://files.pythonhosted.org/packages/55/e3/507a92589994a5b3c3d7f2a7a066339d6ff61c5c839bae56f7eff03d9c7b/PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {url = "https://files.pythonhosted.org/packages/56/8f/e8b49ad21d26111493dc2d5cae4d7efbd0e2e065440665f5023515f87f64/PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, + {url = "https://files.pythonhosted.org/packages/59/00/30e33fcd2a4562cd40c49c7740881009240c5cbbc0e41ca79ca4bba7c24b/PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, + {url = "https://files.pythonhosted.org/packages/5e/f4/7b4bb01873be78fc9fde307f38f62e380b7111862c165372cf094ca2b093/PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {url = "https://files.pythonhosted.org/packages/63/6b/f5dc7942bac17192f4ef00b2d0cdd1ae45eea453d05c1944c0573debe945/PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {url = "https://files.pythonhosted.org/packages/67/d4/b95266228a25ef5bd70984c08b4efce2c035a4baa5ccafa827b266e3dc36/PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {url = "https://files.pythonhosted.org/packages/68/3f/c027422e49433239267c62323fbc6320d6ac8d7d50cf0cb2a376260dad5f/PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, + {url = "https://files.pythonhosted.org/packages/6c/3d/524c642f3db37e7e7ab8d13a3f8b0c72d04a619abc19100097d987378fc6/PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {url = "https://files.pythonhosted.org/packages/74/68/3c13deaa496c14a030c431b7b828d6b343f79eb241b4848c7918091a64a2/PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {url = "https://files.pythonhosted.org/packages/77/da/e845437ffe0dffae4e7562faf23a4f264d886431c5d2a2816c853288dc8e/PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {url = "https://files.pythonhosted.org/packages/7f/d9/6a0d14ac8d3b5605dc925d177c1d21ee9f0b7b39287799db1e50d197b2f4/PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, + {url = "https://files.pythonhosted.org/packages/81/59/561f7e46916b78f3c4cab8d0c307c81656f11e32c846c0c97fda0019ed76/PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {url = "https://files.pythonhosted.org/packages/89/26/0bfd7b756b34c68f8fd158b7bc762b6b1705fc1b3cebf4cdbb53fd9ea75b/PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {url = "https://files.pythonhosted.org/packages/91/49/d46d7b15cddfa98533e89f3832f391aedf7e31f37b4d4df3a7a7855a7073/PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {url = "https://files.pythonhosted.org/packages/9d/f6/7e91fbb58c9ee528759aea5892e062cccb426720c5830ddcce92eba00ff1/PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {url = "https://files.pythonhosted.org/packages/a4/ba/e508fc780e3c94c12753a54fe8f74de535741a10d33b29a576a9bec03500/PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {url = "https://files.pythonhosted.org/packages/a4/e6/4d7a01bc0730c8f958a62d6a4c4f3df23b6139ad68c132b168970d84f192/PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {url = "https://files.pythonhosted.org/packages/a8/32/1bbe38477fb23f1d83041fefeabf93ef1cd6f0efcf44c221519507315d92/PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {url = "https://files.pythonhosted.org/packages/a8/5b/c4d674846ea4b07ee239fbf6010bcc427c4e4552ba5655b446e36b9a40a7/PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {url = "https://files.pythonhosted.org/packages/b3/85/79b9e5b4e8d3c0ac657f4e8617713cca8408f6cdc65d2ee6554217cedff1/PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {url = "https://files.pythonhosted.org/packages/b7/09/2f6f4851bbca08642fef087bade095edc3c47f28d1e7bff6b20de5262a77/PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {url = "https://files.pythonhosted.org/packages/cb/5f/05dd91f5046e2256e35d885f3b8f0f280148568f08e1bf20421887523e9a/PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, + {url = "https://files.pythonhosted.org/packages/d1/c0/4fe04181b0210ee2647cfbb89ecd10a36eef89f10d8aca6a192c201bbe58/PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {url = "https://files.pythonhosted.org/packages/d7/42/7ad4b6d67a16229496d4f6e74201bdbebcf4bc1e87d5a70c9297d4961bd2/PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {url = "https://files.pythonhosted.org/packages/db/4e/74bc723f2d22677387ab90cd9139e62874d14211be7172ed8c9f9a7c81a9/PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {url = "https://files.pythonhosted.org/packages/df/75/ee0565bbf65133e5b6ffa154db43544af96ea4c42439e6b58c1e0eb44b4e/PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {url = "https://files.pythonhosted.org/packages/eb/5f/6e6fe6904e1a9c67bc2ca5629a69e7a5a0b17f079da838bab98a1e548b25/PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {url = "https://files.pythonhosted.org/packages/ef/ad/b443cce94539e57e1a745a845f95c100ad7b97593d7e104051e43f730ecd/PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {url = "https://files.pythonhosted.org/packages/f5/6f/b8b4515346af7c33d3b07cd8ca8ea0700ca72e8d7a750b2b87ac0268ca4e/PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {url = "https://files.pythonhosted.org/packages/f8/54/799b059314b13e1063473f76e908f44106014d18f54b16c83a16edccd5ec/PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, + {url = "https://files.pythonhosted.org/packages/fc/48/531ecd926fe0a374346dd811bf1eda59a95583595bb80eadad511f3269b8/PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, +] +"redis 4.6.0" = [ + {url = "https://files.pythonhosted.org/packages/20/2e/409703d645363352a20c944f5d119bdae3eb3034051a53724a7c5fee12b8/redis-4.6.0-py3-none-any.whl", hash = "sha256:e2b03db868160ee4591de3cb90d40ebb50a90dd302138775937f6a42b7ed183c"}, + {url = "https://files.pythonhosted.org/packages/73/88/63d802c2b18dd9eaa5b846cbf18917c6b2882f20efda398cc16a7500b02c/redis-4.6.0.tar.gz", hash = "sha256:585dc516b9eb042a619ef0a39c3d7d55fe81bdb4df09a52c9cdde0d07bf1aa7d"}, +] +"requests 2.29.0" = [ + {url = "https://files.pythonhosted.org/packages/4c/d2/70fc708727b62d55bc24e43cc85f073039023212d482553d853c44e57bdb/requests-2.29.0.tar.gz", hash = "sha256:f2e34a75f4749019bb0e3effb66683630e4ffeaf75819fb51bebef1bf5aef059"}, + {url = "https://files.pythonhosted.org/packages/cf/e1/2aa539876d9ed0ddc95882451deb57cfd7aa8dbf0b8dbce68e045549ba56/requests-2.29.0-py3-none-any.whl", hash = "sha256:e8f3c9be120d3333921d213eef078af392fba3933ab7ed2d1cba3b56f2568c3b"}, +] +"responses 0.23.1" = [ + {url = "https://files.pythonhosted.org/packages/72/6a/64c85e69c6a7b02e828ed193b2fc15e3ff6581f87501666b98feabc54809/responses-0.23.1-py3-none-any.whl", hash = "sha256:8a3a5915713483bf353b6f4079ba8b2a29029d1d1090a503c70b0dc5d9d0c7bd"}, + {url = "https://files.pythonhosted.org/packages/fa/4f/5033bf66528c832e7fcc48e76f540bf401302c55041c7fb488b4fbaaec4a/responses-0.23.1.tar.gz", hash = "sha256:c4d9aa9fc888188f0c673eff79a8dadbe2e75b7fe879dc80a221a06e0a68138f"}, +] +"rich 13.4.2" = [ + {url = "https://files.pythonhosted.org/packages/e3/12/67d0098eb77005f5e068de639e6f4cfb8f24e6fcb0fd2037df0e1d538fee/rich-13.4.2.tar.gz", hash = "sha256:d653d6bccede5844304c605d5aac802c7cf9621efd700b46c7ec2b51ea914898"}, + {url = "https://files.pythonhosted.org/packages/fc/1e/482e5eec0b89b593e81d78f819a9412849814e22225842b598908e7ac560/rich-13.4.2-py3-none-any.whl", hash = "sha256:8f87bc7ee54675732fa66a05ebfe489e27264caeeff3728c945d25971b6485ec"}, +] +"setuptools 68.0.0" = [ + {url = "https://files.pythonhosted.org/packages/c7/42/be1c7bbdd83e1bfb160c94b9cafd8e25efc7400346cf7ccdbdb452c467fa/setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, + {url = "https://files.pythonhosted.org/packages/dc/98/5f896af066c128669229ff1aa81553ac14cfb3e5e74b6b44594132b8540e/setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, +] +"six 1.16.0" = [ + {url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, + {url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, +] +"sniffio 1.3.0" = [ + {url = "https://files.pythonhosted.org/packages/c3/a0/5dba8ed157b0136607c7f2151db695885606968d1fae123dc3391e0cfdbf/sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, + {url = "https://files.pythonhosted.org/packages/cd/50/d49c388cae4ec10e8109b1b833fd265511840706808576df3ada99ecb0ac/sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, +] +"sqlalchemy 1.4.41" = [ + {url = "https://files.pythonhosted.org/packages/05/f5/23735f8e87c4c66058b327773654930898cdb3e206a8ddb22aadc2e54cea/SQLAlchemy-1.4.41-cp36-cp36m-win32.whl", hash = "sha256:3e2ef592ac3693c65210f8b53d0edcf9f4405925adcfc031ff495e8d18169682"}, + {url = "https://files.pythonhosted.org/packages/07/0d/46d1a6c25fce13d2c6892e9a203d4baae3058cb04396915365d621965f95/SQLAlchemy-1.4.41-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:639e1ae8d48b3c86ffe59c0daa9a02e2bfe17ca3d2b41611b30a0073937d4497"}, + {url = "https://files.pythonhosted.org/packages/08/a8/8146793f1cbe0b7753463e885dd30ad2f647d700530625598355863397b5/SQLAlchemy-1.4.41-cp37-cp37m-win_amd64.whl", hash = "sha256:5323252be2bd261e0aa3f33cb3a64c45d76829989fa3ce90652838397d84197d"}, + {url = "https://files.pythonhosted.org/packages/10/60/e891b496ca0bbbabedcb387d43be52b6b59dfb902a0e2df26d1cc43caf4c/SQLAlchemy-1.4.41-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2d6495f84c4fd11584f34e62f9feec81bf373787b3942270487074e35cbe5330"}, + {url = "https://files.pythonhosted.org/packages/1b/82/53cc4c827ce330ce97767a3536e320e58f8803da3255ba4752ca20d8f376/SQLAlchemy-1.4.41-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:036d8472356e1d5f096c5e0e1a7e0f9182140ada3602f8fff6b7329e9e7cfbcd"}, + {url = "https://files.pythonhosted.org/packages/1d/46/208bb085d3405eaec7aa41e8b3eda0c3aa596169e0d31c7bcc75ad1b9abc/SQLAlchemy-1.4.41-cp37-cp37m-win32.whl", hash = "sha256:0005bd73026cd239fc1e8ccdf54db58b6193be9a02b3f0c5983808f84862c767"}, + {url = "https://files.pythonhosted.org/packages/37/b5/136c78031fb88f3f79fa1090c339f36a7b9bbb359651767b617f2bbf655a/SQLAlchemy-1.4.41-cp311-cp311-win_amd64.whl", hash = "sha256:d2e054aed4645f9b755db85bc69fc4ed2c9020c19c8027976f66576b906a74f1"}, + {url = "https://files.pythonhosted.org/packages/39/ec/02955ea76aca27cba7b280cea29f7952133f154b3a0be50281f125a4c753/SQLAlchemy-1.4.41-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:c23d64a0b28fc78c96289ffbd0d9d1abd48d267269b27f2d34e430ea73ce4b26"}, + {url = "https://files.pythonhosted.org/packages/42/8b/4ddf009cb17231471419d9e31dd03005c0b31f8a4e94a9cd1a0b4ade44d4/SQLAlchemy-1.4.41-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:4ba7e122510bbc07258dc42be6ed45997efdf38129bde3e3f12649be70683546"}, + {url = "https://files.pythonhosted.org/packages/5b/05/0344b99768d345cd92785949a3dac38bfb7059b3b4dc6ae1e55ea842c772/SQLAlchemy-1.4.41-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:361f6b5e3f659e3c56ea3518cf85fbdae1b9e788ade0219a67eeaaea8a4e4d2a"}, + {url = "https://files.pythonhosted.org/packages/5b/3d/4c6da7a76f850c55e9115d5bcf2f90509a8617f4e955d9bd82f23008e029/SQLAlchemy-1.4.41-cp38-cp38-win32.whl", hash = "sha256:58bb65b3274b0c8a02cea9f91d6f44d0da79abc993b33bdedbfec98c8440175a"}, + {url = "https://files.pythonhosted.org/packages/5c/0c/4256c722fc41e7f581776ac05af9b5db5c304c7888d625e47d079024c7b8/SQLAlchemy-1.4.41-cp38-cp38-win_amd64.whl", hash = "sha256:ce8feaa52c1640de9541eeaaa8b5fb632d9d66249c947bb0d89dd01f87c7c288"}, + {url = "https://files.pythonhosted.org/packages/67/a0/97da2cb07e013fd6c37fd896a86b374aa726e4161cafd57185e8418d59aa/SQLAlchemy-1.4.41.tar.gz", hash = "sha256:0292f70d1797e3c54e862e6f30ae474014648bc9c723e14a2fda730adb0a9791"}, + {url = "https://files.pythonhosted.org/packages/73/2e/d61aeec5580ae1841508c39ac63a9a8cfb8200d88f3d9b7d57607ab2f245/SQLAlchemy-1.4.41-cp39-cp39-win_amd64.whl", hash = "sha256:f5fa526d027d804b1f85cdda1eb091f70bde6fb7d87892f6dd5a48925bc88898"}, + {url = "https://files.pythonhosted.org/packages/79/5f/cf2664ea15b04cfacab5f9ed791741874c67d58f69ad86c22488bc53a2f0/SQLAlchemy-1.4.41-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:14576238a5f89bcf504c5f0a388d0ca78df61fb42cb2af0efe239dc965d4f5c9"}, + {url = "https://files.pythonhosted.org/packages/7e/7f/0693241547e0b8534600e831dfe0a8bbcb29a60c53925ed604a747a00bb8/SQLAlchemy-1.4.41-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e16c2be5cb19e2c08da7bd3a87fed2a0d4e90065ee553a940c4fc1a0fb1ab72b"}, + {url = "https://files.pythonhosted.org/packages/85/8a/83f1056449d819532c337a4a1b709a8e6291b9398340c0b2c00d5fdc7589/SQLAlchemy-1.4.41-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:05f0de3a1dc3810a776275763764bb0015a02ae0f698a794646ebc5fb06fad33"}, + {url = "https://files.pythonhosted.org/packages/93/0c/377daa276fa54ad65a6dbd0323285cf0892972fa88a4dbe17113ec440c32/SQLAlchemy-1.4.41-cp311-cp311-win32.whl", hash = "sha256:59bdc291165b6119fc6cdbc287c36f7f2859e6051dd923bdf47b4c55fd2f8bd0"}, + {url = "https://files.pythonhosted.org/packages/a8/62/9f74f13f3907ca416d8fc7b1c33a8137717a2a2d42364038b9437dcc8040/SQLAlchemy-1.4.41-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0002e829142b2af00b4eaa26c51728f3ea68235f232a2e72a9508a3116bd6ed0"}, + {url = "https://files.pythonhosted.org/packages/b1/1a/e0c11a28c2d2c3c1e74705d4fcb2246434050eed69b70e6acf0ef88adbb0/SQLAlchemy-1.4.41-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:22ff16cedab5b16a0db79f1bc99e46a6ddececb60c396562e50aab58ddb2871c"}, + {url = "https://files.pythonhosted.org/packages/b6/df/51a99ba9b419e15aa39948756f79d6ef2df9ede3288799c1deb43b618799/SQLAlchemy-1.4.41-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5102fb9ee2c258a2218281adcb3e1918b793c51d6c2b4666ce38c35101bb940e"}, + {url = "https://files.pythonhosted.org/packages/bc/a9/f9eb3d4952bfa67f7489732af8db2c31b2e99b6b2f70f786fb6d92b18ebb/SQLAlchemy-1.4.41-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:199a73c31ac8ea59937cc0bf3dfc04392e81afe2ec8a74f26f489d268867846c"}, + {url = "https://files.pythonhosted.org/packages/be/76/912622f9e0b87a9fc58d4d58e9ce459bbd9cd83021c51989afb1839d2162/SQLAlchemy-1.4.41-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0990932f7cca97fece8017414f57fdd80db506a045869d7ddf2dda1d7cf69ecc"}, + {url = "https://files.pythonhosted.org/packages/bf/ed/443a8584b15cbab97f0a5e5ba4974c7b6c989d2ec5a37423946a24619bcf/SQLAlchemy-1.4.41-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8eb8897367a21b578b26f5713833836f886817ee2ffba1177d446fa3f77e67c8"}, + {url = "https://files.pythonhosted.org/packages/bf/f2/69c9f96515b4eb65fac522c8b81ec10666ee4789484b0c123452c1f22505/SQLAlchemy-1.4.41-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ad2b727fc41c7f8757098903f85fafb4bf587ca6605f82d9bf5604bd9c7cded"}, + {url = "https://files.pythonhosted.org/packages/ce/b7/1b65516236b36b55624768f7923c9a8d55ca4ba239b795ea84cb82086718/SQLAlchemy-1.4.41-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2307495d9e0ea00d0c726be97a5b96615035854972cc538f6e7eaed23a35886c"}, + {url = "https://files.pythonhosted.org/packages/d0/ea/86e73fb946694c491a332710d0686f3260b941b3af43502457d3a62512dd/SQLAlchemy-1.4.41-cp310-cp310-win32.whl", hash = "sha256:2082a2d2fca363a3ce21cfa3d068c5a1ce4bf720cf6497fb3a9fc643a8ee4ddd"}, + {url = "https://files.pythonhosted.org/packages/d5/4a/29ce9d2ec5bb2d3e83ad387b956defde6229252259795cd28210a5020740/SQLAlchemy-1.4.41-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5ebeeec5c14533221eb30bad716bc1fd32f509196318fb9caa7002c4a364e4c"}, + {url = "https://files.pythonhosted.org/packages/d6/b7/78d3425a6b3aa486c46259228c1933a22ac4d48b0e6220930973ac852091/SQLAlchemy-1.4.41-cp310-cp310-win_amd64.whl", hash = "sha256:e4b12e3d88a8fffd0b4ca559f6d4957ed91bd4c0613a4e13846ab8729dc5c251"}, + {url = "https://files.pythonhosted.org/packages/de/c2/cb1e60fee76b253b396e31a641e117ba689437b1d9dbecfe8415cb0e8b43/SQLAlchemy-1.4.41-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:13e397a9371ecd25573a7b90bd037db604331cf403f5318038c46ee44908c44d"}, + {url = "https://files.pythonhosted.org/packages/e4/3c/b37bbfe25ebfe129cfa7843e74af3081cca6ae9a893869ba82639479fdf9/SQLAlchemy-1.4.41-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0dcf127bb99458a9d211e6e1f0f3edb96c874dd12f2503d4d8e4f1fd103790b"}, + {url = "https://files.pythonhosted.org/packages/e5/5b/fbaf9a5f3ef900f9eb30644cb74520a7771250a1d0b26a44ca053d3ef4fe/SQLAlchemy-1.4.41-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676d51c9f6f6226ae8f26dc83ec291c088fe7633269757d333978df78d931ab"}, + {url = "https://files.pythonhosted.org/packages/ea/4e/4bcd7e756fa2e989e7eed239bca3c3fc57101b7d0c49864f8e41d202d1ce/SQLAlchemy-1.4.41-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b67fc780cfe2b306180e56daaa411dd3186bf979d50a6a7c2a5b5036575cbdbb"}, + {url = "https://files.pythonhosted.org/packages/f0/97/c6a1bc6e80844c10ee1cb599fa5d8c919fc68b9d9ebed22217cadcfca4c8/SQLAlchemy-1.4.41-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cd767cf5d7252b1c88fcfb58426a32d7bd14a7e4942497e15b68ff5d822b41ad"}, + {url = "https://files.pythonhosted.org/packages/f1/81/638d6bd19baf595959c42c154d83262d609140898eb88866db2f024fcc00/SQLAlchemy-1.4.41-cp39-cp39-win32.whl", hash = "sha256:9c56e19780cd1344fcd362fd6265a15f48aa8d365996a37fab1495cae8fcd97d"}, + {url = "https://files.pythonhosted.org/packages/f4/06/78ab18ec859c7dbdb5182b8463ebb3abac932ad086b9dd15fb60958f9a4f/SQLAlchemy-1.4.41-cp27-cp27m-win_amd64.whl", hash = "sha256:5facb7fd6fa8a7353bbe88b95695e555338fb038ad19ceb29c82d94f62775a05"}, + {url = "https://files.pythonhosted.org/packages/f6/ca/6d666434176ff264e750d14b833a7f2243183a8a69f3a25253f1f0052f09/SQLAlchemy-1.4.41-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ccfd238f766a5bb5ee5545a62dd03f316ac67966a6a658efb63eeff8158a4bbf"}, + {url = "https://files.pythonhosted.org/packages/f8/84/f92a2de0e4a7e82acca2bc74c75295fe5f141ea8ba002e2218cea41d2245/SQLAlchemy-1.4.41-cp36-cp36m-win_amd64.whl", hash = "sha256:eb30cf008850c0a26b72bd1b9be6730830165ce049d239cfdccd906f2685f892"}, + {url = "https://files.pythonhosted.org/packages/fa/5f/150ca2e971231624041de73fbc61b0b16f5139530cbff889213cc00f83f8/SQLAlchemy-1.4.41-cp27-cp27m-win32.whl", hash = "sha256:e570cfc40a29d6ad46c9aeaddbdcee687880940a3a327f2c668dd0e4ef0a441d"}, + {url = "https://files.pythonhosted.org/packages/fe/28/f22792eee334cd83a15ef34b825761ee057d330b9b24d3f1496b95faa557/SQLAlchemy-1.4.41-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:90484a2b00baedad361402c257895b13faa3f01780f18f4a104a2f5c413e4536"}, + {url = "https://files.pythonhosted.org/packages/ff/1c/55bf52c1961ce01164835047ed2c09e44b76d1f18a75841715626f2786b1/SQLAlchemy-1.4.41-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:f37fa70d95658763254941ddd30ecb23fc4ec0c5a788a7c21034fc2305dab7cc"}, +] +"sqlalchemy-utils 0.41.1" = [ + {url = "https://files.pythonhosted.org/packages/73/d8/3863fdfe6b27f6c0dffc650aaa2929f313b33aea615b102279fd46ab550b/SQLAlchemy_Utils-0.41.1-py3-none-any.whl", hash = "sha256:6c96b0768ea3f15c0dc56b363d386138c562752b84f647fb8d31a2223aaab801"}, + {url = "https://files.pythonhosted.org/packages/a3/e0/6906a8a9b8e9deb82923e02e2c1f750c567d69a34f6e1fe566792494a682/SQLAlchemy-Utils-0.41.1.tar.gz", hash = "sha256:a2181bff01eeb84479e38571d2c0718eb52042f9afd8c194d0d02877e84b7d74"}, +] +"sqlalchemy2-stubs 0.0.2a34" = [ + {url = "https://files.pythonhosted.org/packages/32/12/ecfcbe41207a2c6d8b9a9cbb62f80f398de3a2d426355fc568665c8ae2d3/sqlalchemy2_stubs-0.0.2a34-py3-none-any.whl", hash = "sha256:a313220ac793404349899faf1272e821a62dbe1d3a029bd444faa8d3e966cd07"}, + {url = "https://files.pythonhosted.org/packages/7c/d9/c22d5ea650badce160cfaebf24b05eb265b13309fb2a5912b374ce83fb51/sqlalchemy2-stubs-0.0.2a34.tar.gz", hash = "sha256:2432137ab2fde1a608df4544f6712427b0b7ff25990cfbbc5a9d1db6c8c6f489"}, +] +"sqlmodel 0.0.8" = [ + {url = "https://files.pythonhosted.org/packages/64/ba/ad07004536e94e71f99aaae5e667bb6f7230f7e0fbc0b0266e88960dda5f/sqlmodel-0.0.8.tar.gz", hash = "sha256:3371b4d1ad59d2ffd0c530582c2140b6c06b090b32af9b9c6412986d7b117036"}, + {url = "https://files.pythonhosted.org/packages/90/63/65f95cf5902ccdfccec99de87666b5e039589c19db7ab62b3770171e5685/sqlmodel-0.0.8-py3-none-any.whl", hash = "sha256:0fd805719e0c5d4f22be32eb3ffc856eca3f7f20e8c7aa3e117ad91684b518ee"}, +] +"sqlparse 0.4.4" = [ + {url = "https://files.pythonhosted.org/packages/65/16/10f170ec641ed852611b6c9441b23d10b5702ab5288371feab3d36de2574/sqlparse-0.4.4.tar.gz", hash = "sha256:d446183e84b8349fa3061f0fe7f06ca94ba65b426946ffebe6e3e8295332420c"}, + {url = "https://files.pythonhosted.org/packages/98/5a/66d7c9305baa9f11857f247d4ba761402cea75db6058ff850ed7128957b7/sqlparse-0.4.4-py3-none-any.whl", hash = "sha256:5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3"}, +] +"starlette 0.19.1" = [ + {url = "https://files.pythonhosted.org/packages/2b/18/405f4fb59119b8efa203c10a04a32a927976b5450cf649c8b4c9d079d21e/starlette-0.19.1.tar.gz", hash = "sha256:c6d21096774ecb9639acad41b86b7706e52ba3bf1dc13ea4ed9ad593d47e24c7"}, + {url = "https://files.pythonhosted.org/packages/f1/9d/1fa96008b302dd3e398f89f3fc5afb19fb0b0f341fefa05c65b3a38d64cf/starlette-0.19.1-py3-none-any.whl", hash = "sha256:5a60c5c2d051f3a8eb546136aa0c9399773a689595e099e0877704d5888279bf"}, +] +"tomli 2.0.1" = [ + {url = "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {url = "https://files.pythonhosted.org/packages/c0/3f/d7af728f075fb08564c5949a9c95e44352e23dee646869fa104a3b2060a3/tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] +"tomlkit 0.11.8" = [ + {url = "https://files.pythonhosted.org/packages/10/37/dd53019ccb72ef7d73fff0bee9e20b16faff9658b47913a35d79e89978af/tomlkit-0.11.8.tar.gz", hash = "sha256:9330fc7faa1db67b541b28e62018c17d20be733177d290a13b24c62d1614e0c3"}, + {url = "https://files.pythonhosted.org/packages/ef/a8/b1c193be753c02e2a94af6e37ee45d3378a74d44fe778c2434a63af92731/tomlkit-0.11.8-py3-none-any.whl", hash = "sha256:8c726c4c202bdb148667835f68d68780b9a003a9ec34167b6c673b38eff2a171"}, +] +"types-pyyaml 6.0.12.10" = [ + {url = "https://files.pythonhosted.org/packages/0a/06/4c384009ff044ce9c439c69abe4498f7caf2155f69cc699073d4fce84a91/types_PyYAML-6.0.12.10-py3-none-any.whl", hash = "sha256:662fa444963eff9b68120d70cda1af5a5f2aa57900003c2006d7626450eaae5f"}, + {url = "https://files.pythonhosted.org/packages/1e/1f/b94c24cd38507a94efb8bd95682a27fbcda543e5afbd11a018305f552a11/types-PyYAML-6.0.12.10.tar.gz", hash = "sha256:ebab3d0700b946553724ae6ca636ea932c1b0868701d4af121630e78d695fc97"}, +] +"typing-extensions 4.7.1" = [ + {url = "https://files.pythonhosted.org/packages/3c/8b/0111dd7d6c1478bf83baa1cab85c686426c7a6274119aceb2bd9d35395ad/typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, + {url = "https://files.pythonhosted.org/packages/ec/6b/63cc3df74987c36fe26157ee12e09e8f9db4de771e0f3404263117e75b95/typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, +] +"tzdata 2023.3" = [ + {url = "https://files.pythonhosted.org/packages/70/e5/81f99b9fced59624562ab62a33df639a11b26c582be78864b339dafa420d/tzdata-2023.3.tar.gz", hash = "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a"}, + {url = "https://files.pythonhosted.org/packages/d5/fb/a79efcab32b8a1f1ddca7f35109a50e4a80d42ac1c9187ab46522b2407d7/tzdata-2023.3-py2.py3-none-any.whl", hash = "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"}, +] +"urllib3 1.26.16" = [ + {url = "https://files.pythonhosted.org/packages/c5/05/c214b32d21c0b465506f95c4f28ccbcba15022e000b043b72b3df7728471/urllib3-1.26.16-py2.py3-none-any.whl", hash = "sha256:8d36afa7616d8ab714608411b4a3b13e58f463aee519024578e062e141dce20f"}, + {url = "https://files.pythonhosted.org/packages/e2/7d/539e6f0cf9f0b95b71dd701a56dae89f768cd39fd8ce0096af3546aeb5a3/urllib3-1.26.16.tar.gz", hash = "sha256:8f135f6502756bde6b2a9b28989df5fbe87c9970cecaa69041edcce7f0589b14"}, +] +"vine 5.0.0" = [ + {url = "https://files.pythonhosted.org/packages/66/b2/8954108816865edf2b1e0d24f3c2c11dfd7232f795bcf1e4164fb8ee5e15/vine-5.0.0.tar.gz", hash = "sha256:7d3b1624a953da82ef63462013bbd271d3eb75751489f9807598e8f340bd637e"}, + {url = "https://files.pythonhosted.org/packages/8d/61/a7badb48186919a9fd7cf0ef427cab6d16e0ed474035c36fa64ddd72bfa2/vine-5.0.0-py2.py3-none-any.whl", hash = "sha256:4c9dceab6f76ed92105027c49c823800dd33cacce13bdedc5b914e3514b7fb30"}, +] +"virtualenv 20.23.1" = [ + {url = "https://files.pythonhosted.org/packages/21/6b/0910aebe4d5c2a27d5a79ab8fae06d22f7e01dff46baf29ced8d080134c3/virtualenv-20.23.1.tar.gz", hash = "sha256:8ff19a38c1021c742148edc4f81cb43d7f8c6816d2ede2ab72af5b84c749ade1"}, + {url = "https://files.pythonhosted.org/packages/2a/5b/f5ba6ec56448dc85abb75b97dc918a621a52d119ade29c8c1b7e916b0cd3/virtualenv-20.23.1-py3-none-any.whl", hash = "sha256:34da10f14fea9be20e0fd7f04aba9732f84e593dac291b757ce42e3368a39419"}, +] +"wcwidth 0.2.6" = [ + {url = "https://files.pythonhosted.org/packages/20/f4/c0584a25144ce20bfcf1aecd041768b8c762c1eb0aa77502a3f0baa83f11/wcwidth-0.2.6-py2.py3-none-any.whl", hash = "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e"}, + {url = "https://files.pythonhosted.org/packages/5e/5f/1e4bd82a9cc1f17b2c2361a2d876d4c38973a997003ba5eb400e8a932b6c/wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"}, +] +"wrapt 1.15.0" = [ + {url = "https://files.pythonhosted.org/packages/0c/6e/f80c23efc625c10460240e31dcb18dd2b34b8df417bc98521fbfd5bc2e9a/wrapt-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923"}, + {url = "https://files.pythonhosted.org/packages/0f/9a/179018bb3f20071f39597cd38fc65d6285d7b89d57f6c51f502048ed28d9/wrapt-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d"}, + {url = "https://files.pythonhosted.org/packages/12/5a/fae60a8bc9b07a3a156989b79e14c58af05ab18375749ee7c12b2f0dddbd/wrapt-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1"}, + {url = "https://files.pythonhosted.org/packages/18/f6/659d7c431a57da9c9a86945834ab2bf512f1d9ebefacea49135a0135ef1a/wrapt-1.15.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034"}, + {url = "https://files.pythonhosted.org/packages/1e/3c/cb96dbcafbf3a27413fb15e0a1997c4610283f895dc01aca955cd2fda8b9/wrapt-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c"}, + {url = "https://files.pythonhosted.org/packages/20/01/baec2650208284603961d61f53ee6ae8e3eff63489c7230dff899376a6f6/wrapt-1.15.0-cp35-cp35m-win_amd64.whl", hash = "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"}, + {url = "https://files.pythonhosted.org/packages/21/42/36c98e9c024978f52c218f22eba1addd199a356ab16548af143d3a72ac0d/wrapt-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09"}, + {url = "https://files.pythonhosted.org/packages/23/0a/9964d7141b8c5e31c32425d3412662a7873aaf0c0964166f4b37b7db51b6/wrapt-1.15.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3"}, + {url = "https://files.pythonhosted.org/packages/29/41/f05bf85417473cf6fe4eec7396c63762e5a457a42102bd1b8af059af6586/wrapt-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd"}, + {url = "https://files.pythonhosted.org/packages/2b/fb/c31489631bb94ac225677c1090f787a4ae367614b5277f13dbfde24b2b69/wrapt-1.15.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975"}, + {url = "https://files.pythonhosted.org/packages/2d/47/16303c59a890696e1a6fd82ba055fc4e0f793fb4815b5003f1f85f7202ce/wrapt-1.15.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7"}, + {url = "https://files.pythonhosted.org/packages/2e/ce/90dcde9ff9238689f111f07b46da2db570252445a781ea147ff668f651b0/wrapt-1.15.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2"}, + {url = "https://files.pythonhosted.org/packages/31/e6/6ac59c5570a7b9aaecb10de39f70dacd0290620330277e60b29edcf8bc9a/wrapt-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2"}, + {url = "https://files.pythonhosted.org/packages/39/ee/2b8d608f2bcf86242daadf5b0b746c11d3657b09892345f10f171b5ca3ac/wrapt-1.15.0-cp35-cp35m-win32.whl", hash = "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559"}, + {url = "https://files.pythonhosted.org/packages/44/a1/40379212a0b678f995fdb4f4f28aeae5724f3212cdfbf97bee8e6fba3f1b/wrapt-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420"}, + {url = "https://files.pythonhosted.org/packages/45/90/a959fa50084d7acc2e628f093c9c2679dd25085aa5085a22592e028b3e06/wrapt-1.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46"}, + {url = "https://files.pythonhosted.org/packages/47/dd/bee4d33058656c0b2e045530224fcddd9492c354af5d20499e5261148dcb/wrapt-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317"}, + {url = "https://files.pythonhosted.org/packages/48/65/0061e7432ca4b635e96e60e27e03a60ddaca3aeccc30e7415fed0325c3c2/wrapt-1.15.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec"}, + {url = "https://files.pythonhosted.org/packages/4a/7b/c63103817bd2f3b0145608ef642ce90d8b6d1e5780d218bce92e93045e06/wrapt-1.15.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653"}, + {url = "https://files.pythonhosted.org/packages/50/eb/af864a01300878f69b4949f8381ad57d5519c1791307e9fd0bc7f5ab50a5/wrapt-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29"}, + {url = "https://files.pythonhosted.org/packages/54/21/282abeb456f22d93533b2d373eeb393298a30b0cb0683fa8a4ed77654273/wrapt-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1"}, + {url = "https://files.pythonhosted.org/packages/55/20/90f5affc2c879db408124ce14b9443b504f961e47a517dff4f24a00df439/wrapt-1.15.0-cp38-cp38-win32.whl", hash = "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b"}, + {url = "https://files.pythonhosted.org/packages/5d/c4/3cc25541ec0404dd1d178e7697a34814d77be1e489cd6f8cb055ac688314/wrapt-1.15.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98"}, + {url = "https://files.pythonhosted.org/packages/65/be/3ae5afe9d78d97595b28914fa7e375ebc6329549d98f02768d5a08f34937/wrapt-1.15.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e"}, + {url = "https://files.pythonhosted.org/packages/6b/b0/bde5400fdf6d18cb7ef527831de0f86ac206c4da1670b67633e5a547b05f/wrapt-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72"}, + {url = "https://files.pythonhosted.org/packages/78/f2/106d90140a93690eab240fae76759d62dae639fcec1bd098eccdb83aa38f/wrapt-1.15.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a"}, + {url = "https://files.pythonhosted.org/packages/7f/b6/6dc0ddacd20337b4ce6ab0d6b0edc7da3898f85c4f97df7f30267e57509e/wrapt-1.15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0"}, + {url = "https://files.pythonhosted.org/packages/81/1e/0bb8f01c6ac5baba66ef1ab65f4644bede856c3c7aede18c896be222151c/wrapt-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba"}, + {url = "https://files.pythonhosted.org/packages/88/f1/4dfaa1ad111d2a48429dca133e46249922ee2f279e9fdd4ab5b149cd6c71/wrapt-1.15.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90"}, + {url = "https://files.pythonhosted.org/packages/8a/1c/740c3ad1b7754dd7213f4df09ccdaf6b19e36da5ff3a269444ba9e103f1b/wrapt-1.15.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9"}, + {url = "https://files.pythonhosted.org/packages/8f/87/ba6dc86e8edb28fd1e314446301802751bd3157e9780385c9eef633994b9/wrapt-1.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e"}, + {url = "https://files.pythonhosted.org/packages/94/55/91dd3a7efbc1db2b07bbfc490d48e8484852c355d55e61e8b1565d7725f6/wrapt-1.15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7"}, + {url = "https://files.pythonhosted.org/packages/96/37/a33c1220e8a298ab18eb070b6a59e4ccc3f7344b434a7ac4bd5d4bdccc97/wrapt-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee"}, + {url = "https://files.pythonhosted.org/packages/9b/50/383c155a05e3e0361d209e3f55ec823f3736c7a46b29923ea33ab85e8d70/wrapt-1.15.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29"}, + {url = "https://files.pythonhosted.org/packages/9d/40/fee1288d654c80fe1bc5ecee1c8d58f761a39bb30c73f1ce106701dd8b0a/wrapt-1.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e"}, + {url = "https://files.pythonhosted.org/packages/a2/3e/ee671ac60945154dfa3a406b8cb5cef2e3b4fa31c7d04edeb92716342026/wrapt-1.15.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0"}, + {url = "https://files.pythonhosted.org/packages/a4/af/8552671e4e7674fcae14bd3976dd9dc6a2b7294730e4a9a94597ac292a21/wrapt-1.15.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248"}, + {url = "https://files.pythonhosted.org/packages/a6/32/f4868adc994648fac4cfe347bcc1381c9afcb1602c8ba0910f36b96c5449/wrapt-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e"}, + {url = "https://files.pythonhosted.org/packages/a7/da/04883b14284c437eac98c7ad2959197f02acbabd57d5ea8ff4893a7c3920/wrapt-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0"}, + {url = "https://files.pythonhosted.org/packages/a9/64/886e512f438f12424b48a3ab23ae2583ec633be6e13eb97b0ccdff8e328a/wrapt-1.15.0-cp310-cp310-win32.whl", hash = "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1"}, + {url = "https://files.pythonhosted.org/packages/aa/24/bbd64ee4e1db9c75ec2a9677c538866f81800bcd2a8abd1a383369369cf5/wrapt-1.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c"}, + {url = "https://files.pythonhosted.org/packages/af/23/cf5dbfd676480fa8fc6eecc4c413183cd8e14369321c5111fec5c12550e9/wrapt-1.15.0-cp39-cp39-win32.whl", hash = "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff"}, + {url = "https://files.pythonhosted.org/packages/af/7f/25913aacbe0c2c68e7354222bdefe4e840489725eb835e311c581396f91f/wrapt-1.15.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f"}, + {url = "https://files.pythonhosted.org/packages/b1/8b/f4c02cf1f841dede987f93c37d42256dc4a82cd07173ad8a5458eee1c412/wrapt-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e"}, + {url = "https://files.pythonhosted.org/packages/b2/b0/a56b129822568d9946e009e8efd53439b9dd38cc1c4af085aa44b2485b40/wrapt-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1"}, + {url = "https://files.pythonhosted.org/packages/b6/0c/435198dbe6961c2343ca725be26b99c8aee615e32c45bc1cb2a960b06183/wrapt-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364"}, + {url = "https://files.pythonhosted.org/packages/b7/3d/9d3cd75f7fc283b6e627c9b0e904189c41ca144185fd8113a1a094dec8ca/wrapt-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86"}, + {url = "https://files.pythonhosted.org/packages/b9/40/975fbb1ab03fa987900bacc365645c4cbead22baddd273b4f5db7f9843d2/wrapt-1.15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c"}, + {url = "https://files.pythonhosted.org/packages/bd/47/57ffe222af59fae1eb56bca7d458b704a9b59380c47f0921efb94dc4786a/wrapt-1.15.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6"}, + {url = "https://files.pythonhosted.org/packages/c3/12/5fabf0014a0f30eb3975b7199ac2731215a40bc8273083f6a89bd6cadec6/wrapt-1.15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc"}, + {url = "https://files.pythonhosted.org/packages/c4/e3/01f879f8e7c1221c72dbd4bfa106624ee3d01cb8cbc82ef57fbb95880cac/wrapt-1.15.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb"}, + {url = "https://files.pythonhosted.org/packages/c7/cd/18d95465323f29e3f3fd3ff84f7acb402a6a61e6caf994dced7140d78f85/wrapt-1.15.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd"}, + {url = "https://files.pythonhosted.org/packages/ca/1c/5caf61431705b3076ca1152abfd6da6304697d7d4fe48bb3448a6decab40/wrapt-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb"}, + {url = "https://files.pythonhosted.org/packages/cd/a0/84b8fe24af8d7f7374d15e0da1cd5502fff59964bbbf34982df0ca2c9047/wrapt-1.15.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f"}, + {url = "https://files.pythonhosted.org/packages/cd/f0/060add4fcb035024f84fb3b5523fb2b119ac08608af3f61dbdda38477900/wrapt-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6"}, + {url = "https://files.pythonhosted.org/packages/cf/b1/3c24fc0f6b589ad8c99cfd1cd3e586ef144e16aaf9381ed952d047a7ee54/wrapt-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475"}, + {url = "https://files.pythonhosted.org/packages/d1/74/3c99ce16947f7af901f6203ab4a3d0908c4db06e800571dabfe8525fa925/wrapt-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752"}, + {url = "https://files.pythonhosted.org/packages/d2/60/9fe25f4cd910ae94e75a1fd4772b058545e107a82629a5ca0f2cd7cc34d5/wrapt-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e"}, + {url = "https://files.pythonhosted.org/packages/d7/4b/1bd4837362d31d402b9bc1a27cdd405baf994dbf9942696f291d2f7eeb73/wrapt-1.15.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8"}, + {url = "https://files.pythonhosted.org/packages/dd/42/9eedee19435dfc0478cdb8bdc71800aab15a297d1074f1aae0d9489adbc3/wrapt-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705"}, + {url = "https://files.pythonhosted.org/packages/dd/e9/85e780a6b70191114b13b129867cec2fab84279f6beb788e130a26e4ca58/wrapt-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079"}, + {url = "https://files.pythonhosted.org/packages/dd/eb/389f9975a6be31ddd19d29128a11f1288d07b624e464598a4b450f8d007e/wrapt-1.15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29"}, + {url = "https://files.pythonhosted.org/packages/de/77/e2ebfa2f46c19094888a364fdb59aeab9d3336a3ad7ccdf542de572d2a35/wrapt-1.15.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094"}, + {url = "https://files.pythonhosted.org/packages/e8/86/fc38e58843159bdda745258d872b1187ad916087369ec57ef93f5e832fa8/wrapt-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7"}, + {url = "https://files.pythonhosted.org/packages/ec/f4/f84538a367105f0a7e507f0c6766d3b15b848fd753647bbf0c206399b322/wrapt-1.15.0-cp311-cp311-win32.whl", hash = "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416"}, + {url = "https://files.pythonhosted.org/packages/ee/25/83f5dcd9f96606521da2d0e7a03a18800264eafb59b569ff109c4d2fea67/wrapt-1.15.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92"}, + {url = "https://files.pythonhosted.org/packages/f6/89/bf77b063c594795aaa056cac7b467463702f346d124d46d7f06e76e8cd97/wrapt-1.15.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8"}, + {url = "https://files.pythonhosted.org/packages/f6/d3/3c6bd4db883537c40eb9d41d738d329d983d049904f708267f3828a60048/wrapt-1.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a"}, + {url = "https://files.pythonhosted.org/packages/f8/49/10013abe31f6892ae57c5cc260f71b7e08f1cc00f0d7b2bcfa482ea74349/wrapt-1.15.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b"}, + {url = "https://files.pythonhosted.org/packages/f8/7d/73e4e3cdb2c780e13f9d87dc10488d7566d8fd77f8d68f0e416bfbd144c7/wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"}, + {url = "https://files.pythonhosted.org/packages/f8/f8/e068dafbb844c1447c55b23c921f3d338cddaba4ea53187a7dd0058452d9/wrapt-1.15.0-py3-none-any.whl", hash = "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640"}, + {url = "https://files.pythonhosted.org/packages/fb/2d/b6fd53b7dbf94d542866cbf1021b9a62595177fc8405fd75e0a5bf3fa3b8/wrapt-1.15.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418"}, + {url = "https://files.pythonhosted.org/packages/fb/bd/ca7fd05a45e7022f3b780a709bbdb081a6138d828ecdb5b7df113a3ad3be/wrapt-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727"}, + {url = "https://files.pythonhosted.org/packages/fd/8a/db55250ad0b536901173d737781e3b5a7cc7063c46b232c2e3a82a33c032/wrapt-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145"}, + {url = "https://files.pythonhosted.org/packages/ff/f6/c044dec6bec4ce64fbc92614c5238dd432780b06293d2efbcab1a349629c/wrapt-1.15.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019"}, +] +"yarl 1.9.2" = [ + {url = "https://files.pythonhosted.org/packages/0e/b1/a65fcf0363ae8c08c0e586772a34cc15b4200bae163eed24258cc95cda90/yarl-1.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:566185e8ebc0898b11f8026447eacd02e46226716229cea8db37496c8cdd26e0"}, + {url = "https://files.pythonhosted.org/packages/10/b1/13887c4fbf885d64f4b8c1bac403338f7c1c07a34e63d767af8d0972c28d/yarl-1.9.2-cp37-cp37m-win32.whl", hash = "sha256:f364d3480bffd3aa566e886587eaca7c8c04d74f6e8933f3f2c996b7f09bee1b"}, + {url = "https://files.pythonhosted.org/packages/11/3d/785761e64dc90fda6feb9bd0459dc55ebe282a7d4564642a4a8ee277e0c0/yarl-1.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a74dcbfe780e62f4b5a062714576f16c2f3493a0394e555ab141bf0d746bb955"}, + {url = "https://files.pythonhosted.org/packages/12/27/efe7476bdadb2564d7775e0895df56f3e3adf39495094750fb319599180e/yarl-1.9.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13414591ff516e04fcdee8dc051c13fd3db13b673c7a4cb1350e6b2ad9639ad3"}, + {url = "https://files.pythonhosted.org/packages/12/c0/18265b85980450b75641a32dd12635485241e295310c1b04e04ac0cc634e/yarl-1.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38a3928ae37558bc1b559f67410df446d1fbfa87318b124bf5032c31e3447b74"}, + {url = "https://files.pythonhosted.org/packages/14/5b/a0bf4a601ddf4073ae0dcd66a90708aaa944a4ad0addf777d9f1fcd6f4f0/yarl-1.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3b078dbe227f79be488ffcfc7a9edb3409d018e0952cf13f15fd6512847f3f7"}, + {url = "https://files.pythonhosted.org/packages/15/5a/5435fe438874f03aa9f559c5173418fbac680b095ac394e88b0825d12ebd/yarl-1.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56ff08ab5df8429901ebdc5d15941b59f6253393cb5da07b4170beefcf1b2528"}, + {url = "https://files.pythonhosted.org/packages/16/dd/3ef5a4c74f9516f2193b0782046802d73c5475ef49678473a608194f3bf1/yarl-1.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2c315df3293cd521033533d242d15eab26583360b58f7ee5d9565f15fee1bef4"}, + {url = "https://files.pythonhosted.org/packages/19/41/97678e848ce963cd3e89c4dcc13900c9afedd42e5c7d9cfb019716f8bb2a/yarl-1.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2b0738fb871812722a0ac2154be1f049c6223b9f6f22eec352996b69775b36d4"}, + {url = "https://files.pythonhosted.org/packages/19/ed/deeec0a15bf1d9a3d2d2102ebb9dbd84dc312a00fbf88564b56b05f266a1/yarl-1.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82aa6264b36c50acfb2424ad5ca537a2060ab6de158a5bd2a72a032cc75b9eb8"}, + {url = "https://files.pythonhosted.org/packages/1d/78/a273c991086df02837676dc68ccf50d56b2fe624d75258d521c651a65d82/yarl-1.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d4e2c6d555e77b37288eaf45b8f60f0737c9efa3452c6c44626a5455aeb250b9"}, + {url = "https://files.pythonhosted.org/packages/25/68/b67d964bc7768f6462b51c05dd879b6c6f6e55168086b948e6e3e6f6928e/yarl-1.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:9b3152f2f5677b997ae6c804b73da05a39daa6a9e85a512e0e6823d81cdad7cc"}, + {url = "https://files.pythonhosted.org/packages/2d/76/d9178fe8fe5823370b26bbd1bbb159c2cc3f7449cade1a50818bcfc98cae/yarl-1.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a96c19c52ff442a808c105901d0bdfd2e28575b3d5f82e2f5fd67e20dc5f4ea"}, + {url = "https://files.pythonhosted.org/packages/30/55/eda822473c6206470a89ca3550efa23202310a2e56317e55afb709008fd5/yarl-1.9.2-cp310-cp310-win32.whl", hash = "sha256:8ea48e0a2f931064469bdabca50c2f578b565fc446f302a79ba6cc0ee7f384d3"}, + {url = "https://files.pythonhosted.org/packages/31/2c/e6af0f7710412e4ed49c1641f04ed1af334d448d51c55150235e3381f0a7/yarl-1.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75df5ef94c3fdc393c6b19d80e6ef1ecc9ae2f4263c09cacb178d871c02a5ba9"}, + {url = "https://files.pythonhosted.org/packages/34/1f/9a915044ec1e13f046e6d023e4dd1ea43316add36e199d46237d7d97cc88/yarl-1.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b03917871bf859a81ccb180c9a2e6c1e04d2f6a51d953e6a5cdd70c93d4e5a2a"}, + {url = "https://files.pythonhosted.org/packages/35/0f/a68344daf90536755f4a890dbbab65dc6ca58c4a0268cf79bd7c5ddc1468/yarl-1.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ec53a0ea2a80c5cd1ab397925f94bff59222aa3cf9c6da938ce05c9ec20428d"}, + {url = "https://files.pythonhosted.org/packages/3b/b2/34e45989fa5fcf406dd471c517697a5bf483fb1bcaebcf2bedd2b86e0cbb/yarl-1.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:b8cc1863402472f16c600e3e93d542b7e7542a540f95c30afd472e8e549fc3f7"}, + {url = "https://files.pythonhosted.org/packages/3b/b4/d20b0444fa0f0e7efdb328d16efd44d03a02427e090d02f936b990c9e9bc/yarl-1.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59723a029760079b7d991a401386390c4be5bfec1e7dd83e25a6a0881859e716"}, + {url = "https://files.pythonhosted.org/packages/3d/ea/041c270d8853572eec3e6ad26fe7a53eb85d68c052bfbc79b42ae5e63f8a/yarl-1.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:149ddea5abf329752ea5051b61bd6c1d979e13fbf122d3a1f9f0c8be6cb6f63c"}, + {url = "https://files.pythonhosted.org/packages/40/2c/aa941cb37ed206f9f46158dcb6398f17a5bcb95124970fd43d017650b9b2/yarl-1.9.2-cp39-cp39-win32.whl", hash = "sha256:b124e2a6d223b65ba8768d5706d103280914d61f5cae3afbc50fc3dfcc016623"}, + {url = "https://files.pythonhosted.org/packages/45/c6/c58e25159d2186247272595ffff1aaba319d10aab20b40429a6e80418328/yarl-1.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:53338749febd28935d55b41bf0bcc79d634881195a39f6b2f767870b72514caf"}, + {url = "https://files.pythonhosted.org/packages/49/d7/3b21ce9742ded3e942bcf48b01ebe29fdcd8eb9dc3296ebfbb77660ee8bb/yarl-1.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf74d08542c3a9ea97bb8f343d4fcbd4d8f91bba5ec9d5d7f792dbe727f88938"}, + {url = "https://files.pythonhosted.org/packages/50/af/93f1b6d02e936d49e664a8eb4374877e5bacfef115c956939729ac9e2ca8/yarl-1.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:832b7e711027c114d79dffb92576acd1bd2decc467dec60e1cac96912602d0e6"}, + {url = "https://files.pythonhosted.org/packages/53/87/f5588bdc6eba3ca4521bd37094563e8442ba2cff3d6b7e5a2cab48fdc96d/yarl-1.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b25322201585c69abc7b0e89e72790469f7dad90d26754717f3310bfe30331c2"}, + {url = "https://files.pythonhosted.org/packages/54/90/8e7f57b0c83805652bd1c26663f9979d12fed8d22516c14c7f3021f97a19/yarl-1.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b9a4e67ad7b646cd6f0938c7ebfd60e481b7410f574c560e455e938d2da8e0f4"}, + {url = "https://files.pythonhosted.org/packages/58/c7/bad405a8b0b2366c3c21d650831d58fadca95af583c6dc1d2349512741cf/yarl-1.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:066c163aec9d3d073dc9ffe5dd3ad05069bcb03fcaab8d221290ba99f9f69ee3"}, + {url = "https://files.pythonhosted.org/packages/5a/3e/53e50f6b492d73dab887428acff54d4ea1be7575bee2d846b932dff459d9/yarl-1.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fcd436ea16fee7d4207c045b1e340020e58a2597301cfbcfdbe5abd2356c2fb"}, + {url = "https://files.pythonhosted.org/packages/5d/6f/06e3e0b1935e8d65e7c52238b5e82b2afb3e067ff69b07a66b909f3b2432/yarl-1.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:674ca19cbee4a82c9f54e0d1eee28116e63bc6fd1e96c43031d11cbab8b2afd5"}, + {url = "https://files.pythonhosted.org/packages/5f/3f/04b3c5e57844fb9c034b09c5cb6d2b43de5d64a093c30529fd233e16cf09/yarl-1.9.2.tar.gz", hash = "sha256:04ab9d4b9f587c06d801c2abfe9317b77cdf996c65a90d5e84ecc45010823571"}, + {url = "https://files.pythonhosted.org/packages/62/c8/b8e048ba98a0f41d46a22060a57f913b4f9ed9c4f6862de36b8137bb67e2/yarl-1.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:783185c75c12a017cc345015ea359cc801c3b29a2966c2655cd12b233bf5a2be"}, + {url = "https://files.pythonhosted.org/packages/63/80/95ae601d7b7f5f6b53174d91d94df865db9166895934d5065e924634dc76/yarl-1.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bf345c3a4f5ba7f766430f97f9cc1320786f19584acc7086491f45524a551ac"}, + {url = "https://files.pythonhosted.org/packages/6a/67/1ea83dd287358d47adc49f2aeb9e4e8ae72bec8ae2604c3bcae1e7fd73de/yarl-1.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0c77533b5ed4bcc38e943178ccae29b9bcf48ffd1063f5821192f23a1bd27b9"}, + {url = "https://files.pythonhosted.org/packages/6b/83/e0cb0cbb37098475fca29b8c5000fed417b67fc2c6dc8d0fa7e32c000c80/yarl-1.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:be6b3fdec5c62f2a67cb3f8c6dbf56bbf3f61c0f046f84645cd1ca73532ea051"}, + {url = "https://files.pythonhosted.org/packages/75/4d/b86c4fd2c689eaeb078de274f6822b02872a5e19557af16257be3eda20ee/yarl-1.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b7232f8dfbd225d57340e441d8caf8652a6acd06b389ea2d3222b8bc89cbfca6"}, + {url = "https://files.pythonhosted.org/packages/78/1d/0554e6d4c8669ca707e93f188111e29cf8a3c97cf2e8e8448ad3b284ae84/yarl-1.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3a53ba34a636a256d767c086ceb111358876e1fb6b50dfc4d3f4951d40133d5"}, + {url = "https://files.pythonhosted.org/packages/7a/ff/b490d9995b23e8e6d773679b8f3c8347defe39570f63f3eb391ad208d853/yarl-1.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6a5883464143ab3ae9ba68daae8e7c5c95b969462bbe42e2464d60e7e2698368"}, + {url = "https://files.pythonhosted.org/packages/7e/8a/62334982016006a6820f2117990e1161d15fb05fb2d924b99d303ab2c8ab/yarl-1.9.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:e65610c5792870d45d7b68c677681376fcf9cc1c289f23e8e8b39c1485384185"}, + {url = "https://files.pythonhosted.org/packages/80/53/e0fcf51992fcce66863db5e3fef698a5235257c565a81a46fe3648abae39/yarl-1.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac9bb4c5ce3975aeac288cfcb5061ce60e0d14d92209e780c93954076c7c4367"}, + {url = "https://files.pythonhosted.org/packages/80/57/1dd9bc12ebaae2d08862c23a80662ecd9d63a61777f10e56f78ddb6ad48a/yarl-1.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:de119f56f3c5f0e2fb4dee508531a32b069a5f2c6e827b272d1e0ff5ac040333"}, + {url = "https://files.pythonhosted.org/packages/84/c1/eaebee42cbcace2d5b5eb103cae668dec1c239f5c82b90da4b3b20f39419/yarl-1.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:646d663eb2232d7909e6601f1a9107e66f9791f290a1b3dc7057818fe44fc2b6"}, + {url = "https://files.pythonhosted.org/packages/88/87/081c39d7b136820ca0a6d9c2bd160e91de01b1b4af2de2069bb51b52538c/yarl-1.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4e2d08f07a3d7d3e12549052eb5ad3eab1c349c53ac51c209a0e5991bbada78"}, + {url = "https://files.pythonhosted.org/packages/92/30/aceb4a4cacb850d2cd1841ec1746f42868886ae0523d0b1f3a7cfa31ef57/yarl-1.9.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3da8a678ca8b96c8606bbb8bfacd99a12ad5dd288bc6f7979baddd62f71c63ef"}, + {url = "https://files.pythonhosted.org/packages/92/e4/4f8d1cada85dbf1aab7123125b0d2f997cd30457f3540c72c311ded740e6/yarl-1.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:494053246b119b041960ddcd20fd76224149cfea8ed8777b687358727911dd33"}, + {url = "https://files.pythonhosted.org/packages/97/ae/7fba1ec8384192095731460dd2dd20ecdc19cbeade8996ef997bd989d5a2/yarl-1.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:63c48f6cef34e6319a74c727376e95626f84ea091f92c0250a98e53e62c77c72"}, + {url = "https://files.pythonhosted.org/packages/98/21/9ef4adf36cfac771518c3bf687bc9b92451bdaf01ec770879f19e7e5b3c7/yarl-1.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:50f33040f3836e912ed16d212f6cc1efb3231a8a60526a407aeb66c1c1956dde"}, + {url = "https://files.pythonhosted.org/packages/a6/a4/451ac414ebe15fd6b49a457c7e01f0a06f9b512c36e4388a9cfb26568fea/yarl-1.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:61016e7d582bc46a5378ffdd02cd0314fb8ba52f40f9cf4d9a5e7dbef88dee18"}, + {url = "https://files.pythonhosted.org/packages/a8/ce/95614d05af568504884e866d772c9f03235711f5a4d7fccfae54ce82d39d/yarl-1.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee4afac41415d52d53a9833ebae7e32b344be72835bbb589018c9e938045a560"}, + {url = "https://files.pythonhosted.org/packages/b3/81/fb394392ec748d8fce66212b29dc2fd9b2fd8e30d56d818a6a866708e534/yarl-1.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a83503934c6273806aed765035716216cc9ab4e0364f7f066227e1aaea90b8d0"}, + {url = "https://files.pythonhosted.org/packages/b7/aa/8b53bceea5454d0b5602ffc81aaf3b80cc2e9b793fe1e054f690beb82429/yarl-1.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22a94666751778629f1ec4280b08eb11815783c63f52092a5953faf73be24191"}, + {url = "https://files.pythonhosted.org/packages/b7/f6/4daab7a2c4b3b4bb9fe6496ab658171cddcfc3f1f24154b64002763fa763/yarl-1.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5415d5a4b080dc9612b1b63cba008db84e908b95848369aa1da3686ae27b6d2b"}, + {url = "https://files.pythonhosted.org/packages/ba/7e/dd4d8a9bd9343ecc7b45d80b134549c801dda119032a8af71d3699eaf070/yarl-1.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e7221580dc1db478464cfeef9b03b95c5852cc22894e418562997df0d074ccc"}, + {url = "https://files.pythonhosted.org/packages/bb/98/9af77ac76f61ced2a4fb243c16cca6d941801927a332fb9d95da3899ed70/yarl-1.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3458a24e4ea3fd8930e934c129b676c27452e4ebda80fbe47b56d8c6c7a63a9e"}, + {url = "https://files.pythonhosted.org/packages/c4/0c/7898c35ca4945fdd416e1dadeda985cc391e4f9298ae5e71c3a5cd88e82d/yarl-1.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:838162460b3a08987546e881a2bfa573960bb559dfa739e7800ceeec92e64417"}, + {url = "https://files.pythonhosted.org/packages/c9/d4/a5280faa1b8e9ad3a52ddc4c9aea94dd718f9c55f1e10cfb14580f5ebb45/yarl-1.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:891c0e3ec5ec881541f6c5113d8df0315ce5440e244a716b95f2525b7b9f3608"}, + {url = "https://files.pythonhosted.org/packages/d0/17/875e45f3369af23ae6a299dab56140c4b5398b76757bc3b8388454277ba2/yarl-1.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:83fcc480d7549ccebe9415d96d9263e2d4226798c37ebd18c930fce43dfb9574"}, + {url = "https://files.pythonhosted.org/packages/d5/8b/5a30baa12464d55b308c684a4a953df6b2190f7733c92719f194fcd42421/yarl-1.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:822b30a0f22e588b32d3120f6d41e4ed021806418b4c9f0bc3048b8c8cb3f92a"}, + {url = "https://files.pythonhosted.org/packages/d9/cb/0bfa73fad2049b6315ace645df2bd0682e20f9eb2dac120c2e9183359aa1/yarl-1.9.2-cp311-cp311-win32.whl", hash = "sha256:a60347f234c2212a9f0361955007fcf4033a75bf600a33c88a0a8e91af77c0e8"}, + {url = "https://files.pythonhosted.org/packages/d9/cf/bf402f68933fec675b608a941752b836bc25fa2ec7d6922a1b1f315214b5/yarl-1.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c56986609b057b4839968ba901944af91b8e92f1725d1a2d77cbac6972b9ed1"}, + {url = "https://files.pythonhosted.org/packages/da/1a/94328f245b0f38913338cfa817826def45c193cfc75c76905392f6b484f8/yarl-1.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1012fa63eb6c032f3ce5d2171c267992ae0c00b9e164efe4d73db818465fac3"}, + {url = "https://files.pythonhosted.org/packages/de/3f/5a8fcff69c8628ce4dab00612981f4c0598fb9aabd90d01a1ebb037bb6f6/yarl-1.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:95d2ecefbcf4e744ea952d073c6922e72ee650ffc79028eb1e320e732898d7e8"}, + {url = "https://files.pythonhosted.org/packages/e4/a7/6ffee644828e01c6d6ae177ac6cba56255bb793f79c4d32082a895bf8b91/yarl-1.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e9fdc7ac0d42bc3ea78818557fab03af6181e076a2944f43c38684b4b6bed8e3"}, + {url = "https://files.pythonhosted.org/packages/e5/12/4fd9a60b167b00a58552020babb638f9b43c514da0227df9fc6bdf16948f/yarl-1.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:32f1d071b3f362c80f1a7d322bfd7b2d11e33d2adf395cc1dd4df36c9c243095"}, + {url = "https://files.pythonhosted.org/packages/e8/3b/38f2427f7ee497e169d7f8bd74c92a6ace98594c6a921b619ccc57703fe5/yarl-1.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5610f80cf43b6202e2c33ba3ec2ee0a2884f8f423c8f4f62906731d876ef4fac"}, + {url = "https://files.pythonhosted.org/packages/e9/12/f4989d778d8dd137fd58f55ab3a5501175896b8670239b4822ae44afd4ed/yarl-1.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:662e6016409828ee910f5d9602a2729a8a57d74b163c89a837de3fea050c7582"}, + {url = "https://files.pythonhosted.org/packages/eb/cb/4970008c85810c7d0e154ac5d746451b04476ac1dd85dc538563a1c04698/yarl-1.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c2ad583743d16ddbdf6bb14b5cd76bf43b0d0006e918809d5d4ddf7bde8dd82"}, + {url = "https://files.pythonhosted.org/packages/ee/8d/55467943a172b97c1b5d9569433c1a70f86f1f9b0f1c6574285f8ad02fc2/yarl-1.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:159d81f22d7a43e6eabc36d7194cb53f2f15f498dbbfa8edc8a3239350f59fe7"}, + {url = "https://files.pythonhosted.org/packages/f0/39/e28eec982b1dfd7d6044e5af6f0ca0c1d5760c82f0667a72b0698237d61f/yarl-1.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c027a6e96ef77d401d8d5a5c8d6bc478e8042f1e448272e8d9752cb0aff8b5c8"}, + {url = "https://files.pythonhosted.org/packages/f0/cc/cf416dff5bd88899a567fea556a5f68ab94cdf525ebe122e0bdba478f2c4/yarl-1.9.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:52a25809fcbecfc63ac9ba0c0fb586f90837f5425edfd1ec9f3372b119585e45"}, + {url = "https://files.pythonhosted.org/packages/f1/0c/c2e07b3a37c4363078a1c7d586b251eec191594a2d24d6e09dae33c1368f/yarl-1.9.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:1b1bba902cba32cdec51fca038fd53f8beee88b77efc373968d1ed021024cc04"}, + {url = "https://files.pythonhosted.org/packages/f2/b1/9a6eeba1a3f35188eac6b7b535f20c06df0f48e78705405d86a0407e75f1/yarl-1.9.2-cp38-cp38-win32.whl", hash = "sha256:f7a3d8146575e08c29ed1cd287068e6d02f1c7bdff8970db96683b9591b86ee7"}, + {url = "https://files.pythonhosted.org/packages/f2/ea/6fd350376ed2581d0cdb11018bad0215cf987817dba69ea9a4bf8adbac6e/yarl-1.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8288d7cd28f8119b07dd49b7230d6b4562f9b61ee9a4ab02221060d21136be80"}, + {url = "https://files.pythonhosted.org/packages/fb/2d/060ab740f64ea6ea2088e375c3046839faaf4bbba2b65a5364668bd765e7/yarl-1.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84e0b1599334b1e1478db01b756e55937d4614f8654311eb26012091be109d59"}, + {url = "https://files.pythonhosted.org/packages/fe/7d/9d85f658b6f7c041ca3ba371d133040c4dc41eb922aef0a6ba917291d187/yarl-1.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aff634b15beff8902d1f918012fc2a42e0dbae6f469fce134c8a0dc51ca423bb"}, +] +"zipp 3.15.0" = [ + {url = "https://files.pythonhosted.org/packages/00/27/f0ac6b846684cecce1ee93d32450c45ab607f65c2e0255f0092032d91f07/zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, + {url = "https://files.pythonhosted.org/packages/5b/fa/c9e82bbe1af6266adf08afb563905eb87cab83fde00a0a08963510621047/zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, +] diff --git a/datajunction-clients/python/pyproject.toml b/datajunction-clients/python/pyproject.toml new file mode 100644 index 000000000..773dba6fc --- /dev/null +++ b/datajunction-clients/python/pyproject.toml @@ -0,0 +1,66 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.pdm.build] +includes = ["datajunction"] + +[project] +name = "datajunction" +dynamic = ["version"] +description = "DataJunction client library for connecting to a DataJunction server" +authors = [ + {name = "DataJunction Authors", email = "yian.shang@gmail.com"}, +] +dependencies = [ + "requests<3.0.0,>=2.28.2", + "pydantic>=1.10.7,<2", + "alive-progress>=3.1.2", +] +requires-python = ">=3.8,<4.0" +readme = "README.md" +license = {text = "MIT"} +classifiers = [ + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", +] + +[project.optional-dependencies] +pandas = ["pandas>=2.0.2"] + +[tool.hatch.version] +path = "datajunction/__about__.py" + +[project.urls] +repository = "https://github.com/DataJunction/dj" + +[tool.pdm.dev-dependencies] +test = [ + "pre-commit>=3.2.2", + "pylint>=2.17.3", + "pytest-asyncio>=0.21.0", + "pytest-cov>=4.0.0", + "pytest-integration>=0.2.3", + "pytest-mock>=3.10.0", + "pytest>=7.3.1", + "responses>=0.23.1", + "fastapi>=0.79.0", + "urllib3<2", + "datajunction-server @ {root:uri}/../../datajunction-server", + "namesgenerator==0.3", +] + +[tool.hatch.metadata] +allow-direct-references = true + +[tool.coverage.run] +source = ['datajunction/'] + +[tool.isort] +src_paths = ["datajunction/", "tests/"] +profile = 'black' diff --git a/datajunction-clients/python/setup.cfg b/datajunction-clients/python/setup.cfg new file mode 100644 index 000000000..814ec3fa1 --- /dev/null +++ b/datajunction-clients/python/setup.cfg @@ -0,0 +1,19 @@ +# This file is used to configure your project. +# Read more about the various options under: +# https://setuptools.pypa.io/en/latest/userguide/declarative_config.html +# https://setuptools.pypa.io/en/latest/references/keywords.html + +[tool:pytest] +# Specify command line options as you would do when invoking pytest directly. +# e.g. --cov-report html (or xml) for html/xml output or --junitxml junit.xml +# in order to write a coverage file that can be read by Jenkins. +# CAUTION: --cov flags may prohibit setting breakpoints while debugging. +# Comment those flags to avoid this pytest issue. +addopts = + --cov datajunction --cov-report term-missing + --verbose +norecursedirs = + dist + build + .tox +testpaths = tests diff --git a/tests/__init__.py b/datajunction-clients/python/tests/__init__.py similarity index 100% rename from tests/__init__.py rename to datajunction-clients/python/tests/__init__.py diff --git a/datajunction-clients/python/tests/conftest.py b/datajunction-clients/python/tests/conftest.py new file mode 100644 index 000000000..b2ef391b2 --- /dev/null +++ b/datajunction-clients/python/tests/conftest.py @@ -0,0 +1,211 @@ +""" +Fixtures for testing DJ client. +""" +# pylint: disable=redefined-outer-name, invalid-name, W0611 + +from http.client import HTTPException +from typing import Iterator, List, Optional +from unittest.mock import MagicMock + +import pytest +from cachelib import SimpleCache +from datajunction_server.api.main import app +from datajunction_server.config import Settings +from datajunction_server.models import Column, Engine +from datajunction_server.models.materialization import MaterializationInfo +from datajunction_server.models.query import QueryCreate, QueryWithResults +from datajunction_server.service_clients import QueryServiceClient +from datajunction_server.typing import QueryState +from datajunction_server.utils import ( + get_query_service_client, + get_session, + get_settings, +) +from pytest_mock import MockerFixture +from sqlalchemy import create_engine +from sqlalchemy.pool import StaticPool +from sqlmodel import Session, SQLModel +from starlette.testclient import TestClient + +from tests.examples import COLUMN_MAPPINGS, EXAMPLES, QUERY_DATA_MAPPINGS + + +@pytest.fixture +def settings(mocker: MockerFixture) -> Iterator[Settings]: + """ + Custom settings for unit tests. + """ + settings = Settings( + index="sqlite://", + repository="/path/to/repository", + results_backend=SimpleCache(default_timeout=0), + celery_broker=None, + redis_cache=None, + query_service=None, + ) + + mocker.patch( + "datajunction_server.utils.get_settings", + return_value=settings, + ) + + yield settings + + +@pytest.fixture +def session() -> Iterator[Session]: + """ + Create an in-memory SQLite session to test models. + """ + engine = create_engine( + "sqlite://", + connect_args={"check_same_thread": False}, + poolclass=StaticPool, + ) + SQLModel.metadata.create_all(engine) + + with Session(engine, autoflush=False) as session: + yield session + + +@pytest.fixture +def query_service_client(mocker: MockerFixture) -> Iterator[QueryServiceClient]: + """ + Custom settings for unit tests. + """ + qs_client = QueryServiceClient(uri="query_service:8001") + qs_client.query_state = QueryState.RUNNING # type: ignore + + def mock_get_columns_for_table( + catalog: str, + schema: str, + table: str, + engine: Optional[Engine] = None, # pylint: disable=unused-argument + ) -> List[Column]: + return COLUMN_MAPPINGS[f"{catalog}.{schema}.{table}"] + + mocker.patch.object( + qs_client, + "get_columns_for_table", + mock_get_columns_for_table, + ) + + def mock_submit_query( + query_create: QueryCreate, + ) -> QueryWithResults: + results = QUERY_DATA_MAPPINGS[ + query_create.submitted_query.strip() + .replace('"', "") + .replace("\n", "") + .replace(" ", "") + ] + if isinstance(results, Exception): + raise results + + if results.state not in (QueryState.FAILED,): + results.state = qs_client.query_state # type: ignore + qs_client.query_state = QueryState.FINISHED # type: ignore + return results + + mocker.patch.object( + qs_client, + "submit_query", + mock_submit_query, + ) + + mock_materialize = MagicMock() + mock_materialize.return_value = MaterializationInfo( + urls=["http://fake.url/job"], + output_tables=["common.a", "common.b"], + ) + mocker.patch.object( + qs_client, + "materialize", + mock_materialize, + ) + + mock_deactivate_materialization = MagicMock() + mock_deactivate_materialization.return_value = MaterializationInfo( + urls=["http://fake.url/job"], + output_tables=[], + ) + mocker.patch.object( + qs_client, + "deactivate_materialization", + mock_deactivate_materialization, + ) + + mock_get_materialization_info = MagicMock() + mock_get_materialization_info.return_value = MaterializationInfo( + urls=["http://fake.url/job"], + output_tables=["common.a", "common.b"], + ) + mocker.patch.object( + qs_client, + "get_materialization_info", + mock_get_materialization_info, + ) + yield qs_client + + +@pytest.fixture +def server( # pylint: disable=too-many-statements + session: Session, + settings: Settings, + query_service_client: QueryServiceClient, +) -> TestClient: + """ + Create a mock server for testing APIs that contains a mock query service. + """ + + def get_query_service_client_override() -> QueryServiceClient: + return query_service_client + + def get_session_override() -> Session: + return session + + def get_settings_override() -> Settings: + return settings + + app.dependency_overrides[get_session] = get_session_override + app.dependency_overrides[get_settings] = get_settings_override + app.dependency_overrides[ + get_query_service_client + ] = get_query_service_client_override + + with TestClient(app) as test_client: + yield test_client + + app.dependency_overrides.clear() + + +def post_and_raise_if_error(server: TestClient, endpoint: str, json: dict): + """ + Post the payload to the client and raise if there's an error + """ + response = server.post(endpoint, json=json) + if not response.ok: + raise HTTPException(response.text) + + +@pytest.fixture +def session_with_examples(server: TestClient) -> TestClient: + """ + load examples + """ + for endpoint, json in EXAMPLES: + post_and_raise_if_error(server=server, endpoint=endpoint, json=json) # type: ignore + return server + + +def pytest_addoption(parser): + """ + Add flags + """ + parser.addoption( + "--integration", + action="store_true", + dest="integration", + default=False, + help="Run integration tests", + ) diff --git a/datajunction-clients/python/tests/examples.py b/datajunction-clients/python/tests/examples.py new file mode 100644 index 000000000..fa7f4eea9 --- /dev/null +++ b/datajunction-clients/python/tests/examples.py @@ -0,0 +1,1214 @@ +""" +Roads database examples loaded into DJ test session +""" +from typing import Dict, Union + +from datajunction_server.errors import DJException, DJQueryServiceClientException +from datajunction_server.models import Column +from datajunction_server.models.query import QueryWithResults +from datajunction_server.sql.parsing.types import IntegerType, StringType, TimestampType +from datajunction_server.typing import QueryState + +# pylint: disable=too-many-lines + +EXAMPLES = ( # type: ignore + ( + "/catalogs/", + {"name": "draft"}, + ), + ( + "/catalogs/", + {"name": "default"}, + ), + ( + "/engines/", + {"name": "spark", "version": "3.1.1", "dialect": "spark"}, + ), + ( + "/catalogs/default/engines/", + [{"name": "spark", "version": "3.1.1", "dialect": "spark"}], + ), + ( + "/catalogs/", + {"name": "public"}, + ), + ( + "/engines/", + {"name": "postgres", "version": "15.2"}, + ), + ( + "/catalogs/public/engines/", + [{"name": "postgres", "version": "15.2"}], + ), + ( # DJ must be primed with a "default" namespace + "/namespaces/default/", + {}, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "repair_order_id", "type": "int"}, + {"name": "municipality_id", "type": "string"}, + {"name": "hard_hat_id", "type": "int"}, + {"name": "order_date", "type": "timestamp"}, + {"name": "required_date", "type": "timestamp"}, + {"name": "dispatched_date", "type": "timestamp"}, + {"name": "dispatcher_id", "type": "int"}, + ], + "description": "All repair orders", + "mode": "published", + "name": "default.repair_orders", + "catalog": "default", + "schema_": "roads", + "table": "repair_orders", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "repair_order_id", "type": "int"}, + {"name": "repair_type_id", "type": "int"}, + {"name": "price", "type": "float"}, + {"name": "quantity", "type": "int"}, + {"name": "discount", "type": "float"}, + ], + "description": "Details on repair orders", + "mode": "published", + "name": "default.repair_order_details", + "catalog": "default", + "schema_": "roads", + "table": "repair_order_details", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "repair_type_id", "type": "int"}, + {"name": "repair_type_name", "type": "string"}, + {"name": "contractor_id", "type": "int"}, + ], + "description": "Information on types of repairs", + "mode": "published", + "name": "default.repair_type", + "catalog": "default", + "schema_": "roads", + "table": "repair_type", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "contractor_id", "type": "int"}, + {"name": "company_name", "type": "string"}, + {"name": "contact_name", "type": "string"}, + {"name": "contact_title", "type": "string"}, + {"name": "address", "type": "string"}, + {"name": "city", "type": "string"}, + {"name": "state", "type": "string"}, + {"name": "postal_code", "type": "string"}, + {"name": "country", "type": "string"}, + {"name": "phone", "type": "string"}, + ], + "description": "Information on contractors", + "mode": "published", + "name": "default.contractors", + "catalog": "default", + "schema_": "roads", + "table": "contractors", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "municipality_id", "type": "string"}, + {"name": "municipality_type_id", "type": "string"}, + ], + "description": "Lookup table for municipality and municipality types", + "mode": "published", + "name": "default.municipality_municipality_type", + "catalog": "default", + "schema_": "roads", + "table": "municipality_municipality_type", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "municipality_type_id", "type": "string"}, + {"name": "municipality_type_desc", "type": "string"}, + ], + "description": "Information on municipality types", + "mode": "published", + "name": "default.municipality_type", + "catalog": "default", + "schema_": "roads", + "table": "municipality_type", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "municipality_id", "type": "string"}, + {"name": "contact_name", "type": "string"}, + {"name": "contact_title", "type": "string"}, + {"name": "local_region", "type": "string"}, + {"name": "phone", "type": "string"}, + {"name": "state_id", "type": "int"}, + ], + "description": "Information on municipalities", + "mode": "published", + "name": "default.municipality", + "catalog": "default", + "schema_": "roads", + "table": "municipality", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "dispatcher_id", "type": "int"}, + {"name": "company_name", "type": "string"}, + {"name": "phone", "type": "string"}, + ], + "description": "Information on dispatchers", + "mode": "published", + "name": "default.dispatchers", + "catalog": "default", + "schema_": "roads", + "table": "dispatchers", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "hard_hat_id", "type": "int"}, + {"name": "last_name", "type": "string"}, + {"name": "first_name", "type": "string"}, + {"name": "title", "type": "string"}, + {"name": "birth_date", "type": "timestamp"}, + {"name": "hire_date", "type": "timestamp"}, + {"name": "address", "type": "string"}, + {"name": "city", "type": "string"}, + {"name": "state", "type": "string"}, + {"name": "postal_code", "type": "string"}, + {"name": "country", "type": "string"}, + {"name": "manager", "type": "int"}, + {"name": "contractor_id", "type": "int"}, + ], + "description": "Information on employees", + "mode": "published", + "name": "default.hard_hats", + "catalog": "default", + "schema_": "roads", + "table": "hard_hats", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "hard_hat_id", "type": "int"}, + {"name": "state_id", "type": "string"}, + ], + "description": "Lookup table for employee's current state", + "mode": "published", + "name": "default.hard_hat_state", + "catalog": "default", + "schema_": "roads", + "table": "hard_hat_state", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "state_id", "type": "int"}, + {"name": "state_name", "type": "string"}, + {"name": "state_abbr", "type": "string"}, + {"name": "state_region", "type": "int"}, + ], + "description": "Information on different types of repairs", + "mode": "published", + "name": "default.us_states", + "catalog": "default", + "schema_": "roads", + "table": "us_states", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "us_region_id", "type": "int"}, + {"name": "us_region_description", "type": "string"}, + ], + "description": "Information on US regions", + "mode": "published", + "name": "default.us_region", + "catalog": "default", + "schema_": "roads", + "table": "us_region", + }, + ), + ( + "/nodes/dimension/", + { + "description": "Repair order dimension", + "query": """ + SELECT + repair_order_id, + municipality_id, + hard_hat_id, + order_date, + required_date, + dispatched_date, + dispatcher_id + FROM default.repair_orders + """, + "mode": "published", + "name": "default.repair_order", + "primary_key": ["repair_order_id"], + }, + ), + ( + "/nodes/dimension/", + { + "description": "Contractor dimension", + "query": """ + SELECT + contractor_id, + company_name, + contact_name, + contact_title, + address, + city, + state, + postal_code, + country, + phone + FROM default.contractors + """, + "mode": "published", + "name": "default.contractor", + "primary_key": ["contractor_id"], + }, + ), + ( + "/nodes/dimension/", + { + "description": "Hard hat dimension", + "query": """ + SELECT + hard_hat_id, + last_name, + first_name, + title, + birth_date, + hire_date, + address, + city, + state, + postal_code, + country, + manager, + contractor_id + FROM default.hard_hats + """, + "mode": "published", + "name": "default.hard_hat", + "primary_key": ["hard_hat_id"], + }, + ), + ( + "/nodes/dimension/", + { + "description": "Hard hat dimension", + "query": """ + SELECT + hh.hard_hat_id, + last_name, + first_name, + title, + birth_date, + hire_date, + address, + city, + state, + postal_code, + country, + manager, + contractor_id, + hhs.state_id AS state_id + FROM default.hard_hats hh + LEFT JOIN default.hard_hat_state hhs + ON hh.hard_hat_id = hhs.hard_hat_id + WHERE hh.state_id = 'NY' + """, + "mode": "published", + "name": "default.local_hard_hats", + "primary_key": ["hard_hat_id"], + }, + ), + ( + "/nodes/dimension/", + { + "description": "US state dimension", + "query": """ + SELECT + state_id, + state_name, + state_abbr AS state_short, + state_region, + r.us_region_description AS state_region_description + FROM default.us_states s + LEFT JOIN default.us_region r + ON s.state_region = r.us_region_id + """, + "mode": "published", + "name": "default.us_state", + "primary_key": ["state_id"], + }, + ), + ( + "/nodes/dimension/", + { + "description": "Dispatcher dimension", + "query": """ + SELECT + dispatcher_id, + company_name, + phone + FROM default.dispatchers + """, + "mode": "published", + "name": "default.dispatcher", + "primary_key": ["dispatcher_id"], + }, + ), + ( + "/nodes/dimension/", + { + "description": "Municipality dimension", + "query": """ + SELECT + m.municipality_id AS municipality_id, + contact_name, + contact_title, + local_region, + state_id, + mmt.municipality_type_id AS municipality_type_id, + mt.municipality_type_desc AS municipality_type_desc + FROM default.municipality AS m + LEFT JOIN default.municipality_municipality_type AS mmt + ON m.municipality_id = mmt.municipality_id + LEFT JOIN default.municipality_type AS mt + ON mmt.municipality_type_id = mt.municipality_type_desc + """, + "mode": "published", + "name": "default.municipality_dim", + "primary_key": ["municipality_id"], + }, + ), + ( + "/nodes/metric/", + { + "description": "Number of repair orders", + "query": ("SELECT count(repair_order_id) " "FROM default.repair_orders"), + "mode": "published", + "name": "default.num_repair_orders", + }, + ), + ( + "/nodes/metric/", + { + "description": "Average repair price", + "query": ( + "SELECT avg(price) as default_DOT_avg_repair_price " + "FROM default.repair_order_details" + ), + "mode": "published", + "name": "default.avg_repair_price", + }, + ), + ( + "/nodes/metric/", + { + "description": "Total repair cost", + "query": ( + "SELECT sum(price) as default_DOT_total_repair_cost " + "FROM default.repair_order_details" + ), + "mode": "published", + "name": "default.total_repair_cost", + }, + ), + ( + "/nodes/metric/", + { + "description": "Average length of employment", + "query": ( + "SELECT avg(NOW() - hire_date) as default_DOT_avg_length_of_employment " + "FROM default.hard_hats" + ), + "mode": "published", + "name": "default.avg_length_of_employment", + }, + ), + ( + "/nodes/metric/", + { + "description": "Total repair order discounts", + "query": ( + "SELECT sum(price * discount) " "FROM default.repair_order_details" + ), + "mode": "published", + "name": "default.total_repair_order_discounts", + }, + ), + ( + "/nodes/metric/", + { + "description": "Total repair order discounts", + "query": ( + "SELECT avg(price * discount) " "FROM default.repair_order_details" + ), + "mode": "published", + "name": "default.avg_repair_order_discounts", + }, + ), + ( + "/nodes/metric/", + { + "description": "Average time to dispatch a repair order", + "query": ( + "SELECT avg(dispatched_date - order_date) " "FROM default.repair_orders" + ), + "mode": "published", + "name": "default.avg_time_to_dispatch", + }, + ), + ( + ( + "/nodes/default.repair_order_details/columns/repair_order_id/" + "?dimension=default.repair_order&dimension_column=repair_order_id" + ), + {}, + ), + ( + ( + "/nodes/default.repair_orders/columns/municipality_id/" + "?dimension=default.municipality_dim&dimension_column=municipality_id" + ), + {}, + ), + ( + ( + "/nodes/default.repair_type/columns/contractor_id/" + "?dimension=default.contractor&dimension_column=contractor_id" + ), + {}, + ), + ( + ( + "/nodes/default.repair_orders/columns/hard_hat_id/" + "?dimension=default.hard_hat&dimension_column=hard_hat_id" + ), + {}, + ), + ( + ( + "/nodes/default.repair_orders/columns/dispatcher_id/" + "?dimension=default.dispatcher&dimension_column=dispatcher_id" + ), + {}, + ), + ( + ( + "/nodes/default.hard_hat/columns/state/" + "?dimension=default.us_state&dimension_column=state_short" + ), + {}, + ), + ( + ( + "/nodes/default.repair_order_details/columns/repair_order_id/" + "?dimension=default.repair_order&dimension_column=repair_order_id" + ), + {}, + ), + ( + ( + "/nodes/default.repair_order/columns/dispatcher_id/" + "?dimension=default.dispatcher&dimension_column=dispatcher_id" + ), + {}, + ), + ( + ( + "/nodes/default.repair_order/columns/hard_hat_id/" + "?dimension=default.hard_hat&dimension_column=hard_hat_id" + ), + {}, + ), + ( + ( + "/nodes/default.repair_order/columns/municipality_id/" + "?dimension=default.municipality_dim&dimension_column=municipality_id" + ), + {}, + ), + ( # foo.bar Namespaced copy of roads database example + "/namespaces/foo.bar/", + {}, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "repair_order_id", "type": "int"}, + {"name": "municipality_id", "type": "string"}, + {"name": "hard_hat_id", "type": "int"}, + {"name": "order_date", "type": "timestamp"}, + {"name": "required_date", "type": "timestamp"}, + {"name": "dispatched_date", "type": "timestamp"}, + {"name": "dispatcher_id", "type": "int"}, + ], + "description": "All repair orders", + "mode": "published", + "name": "foo.bar.repair_orders", + "catalog": "default", + "schema_": "roads", + "table": "repair_orders", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "repair_order_id", "type": "int"}, + {"name": "repair_type_id", "type": "int"}, + {"name": "price", "type": "float"}, + {"name": "quantity", "type": "int"}, + {"name": "discount", "type": "float"}, + ], + "description": "Details on repair orders", + "mode": "published", + "name": "foo.bar.repair_order_details", + "catalog": "default", + "schema_": "roads", + "table": "repair_order_details", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "repair_type_id", "type": "int"}, + {"name": "repair_type_name", "type": "string"}, + {"name": "contractor_id", "type": "int"}, + ], + "description": "Information on types of repairs", + "mode": "published", + "name": "foo.bar.repair_type", + "catalog": "default", + "schema_": "roads", + "table": "repair_type", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "contractor_id", "type": "int"}, + {"name": "company_name", "type": "string"}, + {"name": "contact_name", "type": "string"}, + {"name": "contact_title", "type": "string"}, + {"name": "address", "type": "string"}, + {"name": "city", "type": "string"}, + {"name": "state", "type": "string"}, + {"name": "postal_code", "type": "string"}, + {"name": "country", "type": "string"}, + {"name": "phone", "type": "string"}, + ], + "description": "Information on contractors", + "mode": "published", + "name": "foo.bar.contractors", + "catalog": "default", + "schema_": "roads", + "table": "contractors", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "municipality_id", "type": "string"}, + {"name": "municipality_type_id", "type": "string"}, + ], + "description": "Lookup table for municipality and municipality types", + "mode": "published", + "name": "foo.bar.municipality_municipality_type", + "catalog": "default", + "schema_": "roads", + "table": "municipality_municipality_type", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "municipality_type_id", "type": "string"}, + {"name": "municipality_type_desc", "type": "string"}, + ], + "description": "Information on municipality types", + "mode": "published", + "name": "foo.bar.municipality_type", + "catalog": "default", + "schema_": "roads", + "table": "municipality_type", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "municipality_id", "type": "string"}, + {"name": "contact_name", "type": "string"}, + {"name": "contact_title", "type": "string"}, + {"name": "local_region", "type": "string"}, + {"name": "phone", "type": "string"}, + {"name": "state_id", "type": "int"}, + ], + "description": "Information on municipalities", + "mode": "published", + "name": "foo.bar.municipality", + "catalog": "default", + "schema_": "roads", + "table": "municipality", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "dispatcher_id", "type": "int"}, + {"name": "company_name", "type": "string"}, + {"name": "phone", "type": "string"}, + ], + "description": "Information on dispatchers", + "mode": "published", + "name": "foo.bar.dispatchers", + "catalog": "default", + "schema_": "roads", + "table": "dispatchers", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "hard_hat_id", "type": "int"}, + {"name": "last_name", "type": "string"}, + {"name": "first_name", "type": "string"}, + {"name": "title", "type": "string"}, + {"name": "birth_date", "type": "timestamp"}, + {"name": "hire_date", "type": "timestamp"}, + {"name": "address", "type": "string"}, + {"name": "city", "type": "string"}, + {"name": "state", "type": "string"}, + {"name": "postal_code", "type": "string"}, + {"name": "country", "type": "string"}, + {"name": "manager", "type": "int"}, + {"name": "contractor_id", "type": "int"}, + ], + "description": "Information on employees", + "mode": "published", + "name": "foo.bar.hard_hats", + "catalog": "default", + "schema_": "roads", + "table": "hard_hats", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "hard_hat_id", "type": "int"}, + {"name": "state_id", "type": "string"}, + ], + "description": "Lookup table for employee's current state", + "mode": "published", + "name": "foo.bar.hard_hat_state", + "catalog": "default", + "schema_": "roads", + "table": "hard_hat_state", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "state_id", "type": "int"}, + {"name": "state_name", "type": "string"}, + {"name": "state_abbr", "type": "string"}, + {"name": "state_region", "type": "int"}, + ], + "description": "Information on different types of repairs", + "mode": "published", + "name": "foo.bar.us_states", + "catalog": "default", + "schema_": "roads", + "table": "us_states", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "us_region_id", "type": "int"}, + {"name": "us_region_description", "type": "string"}, + ], + "description": "Information on US regions", + "mode": "published", + "name": "foo.bar.us_region", + "catalog": "default", + "schema_": "roads", + "table": "us_region", + }, + ), + ( + "/nodes/dimension/", + { + "description": "Repair order dimension", + "query": """ + SELECT + repair_order_id, + municipality_id, + hard_hat_id, + order_date, + required_date, + dispatched_date, + dispatcher_id + FROM foo.bar.repair_orders + """, + "mode": "published", + "name": "foo.bar.repair_order", + "primary_key": ["repair_order_id"], + }, + ), + ( + "/nodes/dimension/", + { + "description": "Contractor dimension", + "query": """ + SELECT + contractor_id, + company_name, + contact_name, + contact_title, + address, + city, + state, + postal_code, + country, + phone + FROM foo.bar.contractors + """, + "mode": "published", + "name": "foo.bar.contractor", + "primary_key": ["contractor_id"], + }, + ), + ( + "/nodes/dimension/", + { + "description": "Hard hat dimension", + "query": """ + SELECT + hard_hat_id, + last_name, + first_name, + title, + birth_date, + hire_date, + address, + city, + state, + postal_code, + country, + manager, + contractor_id + FROM foo.bar.hard_hats + """, + "mode": "published", + "name": "foo.bar.hard_hat", + "primary_key": ["hard_hat_id"], + }, + ), + ( + "/nodes/dimension/", + { + "description": "Hard hat dimension", + "query": """ + SELECT + hh.hard_hat_id, + last_name, + first_name, + title, + birth_date, + hire_date, + address, + city, + state, + postal_code, + country, + manager, + contractor_id, + hhs.state_id AS state_id + FROM foo.bar.hard_hats hh + LEFT JOIN foo.bar.hard_hat_state hhs + ON hh.hard_hat_id = hhs.hard_hat_id + WHERE hh.state_id = 'NY' + """, + "mode": "published", + "name": "foo.bar.local_hard_hats", + "primary_key": ["hard_hat_id"], + }, + ), + ( + "/nodes/dimension/", + { + "description": "US state dimension", + "query": """ + SELECT + state_id, + state_name, + state_abbr, + state_region, + r.us_region_description AS state_region_description + FROM foo.bar.us_states s + LEFT JOIN foo.bar.us_region r + ON s.state_region = r.us_region_id + """, + "mode": "published", + "name": "foo.bar.us_state", + "primary_key": ["state_id"], + }, + ), + ( + "/nodes/dimension/", + { + "description": "Dispatcher dimension", + "query": """ + SELECT + dispatcher_id, + company_name, + phone + FROM foo.bar.dispatchers + """, + "mode": "published", + "name": "foo.bar.dispatcher", + "primary_key": ["dispatcher_id"], + }, + ), + ( + "/nodes/dimension/", + { + "description": "Municipality dimension", + "query": """ + SELECT + m.municipality_id AS municipality_id, + contact_name, + contact_title, + local_region, + state_id, + mmt.municipality_type_id AS municipality_type_id, + mt.municipality_type_desc AS municipality_type_desc + FROM foo.bar.municipality AS m + LEFT JOIN foo.bar.municipality_municipality_type AS mmt + ON m.municipality_id = mmt.municipality_id + LEFT JOIN foo.bar.municipality_type AS mt + ON mmt.municipality_type_id = mt.municipality_type_desc + """, + "mode": "published", + "name": "foo.bar.municipality_dim", + "primary_key": ["municipality_id"], + }, + ), + ( + "/nodes/metric/", + { + "description": "Number of repair orders", + "query": ("SELECT count(repair_order_id) " "FROM foo.bar.repair_orders"), + "mode": "published", + "name": "foo.bar.num_repair_orders", + }, + ), + ( + "/nodes/metric/", + { + "description": "Average repair price", + "query": "SELECT avg(price) FROM foo.bar.repair_order_details", + "mode": "published", + "name": "foo.bar.avg_repair_price", + }, + ), + ( + "/nodes/metric/", + { + "description": "Total repair cost", + "query": "SELECT sum(price) FROM foo.bar.repair_order_details", + "mode": "published", + "name": "foo.bar.total_repair_cost", + }, + ), + ( + "/nodes/metric/", + { + "description": "Average length of employment", + "query": ("SELECT avg(NOW() - hire_date) " "FROM foo.bar.hard_hats"), + "mode": "published", + "name": "foo.bar.avg_length_of_employment", + }, + ), + ( + "/nodes/metric/", + { + "description": "Total repair order discounts", + "query": ( + "SELECT sum(price * discount) " "FROM foo.bar.repair_order_details" + ), + "mode": "published", + "name": "foo.bar.total_repair_order_discounts", + }, + ), + ( + "/nodes/metric/", + { + "description": "Total repair order discounts", + "query": ( + "SELECT avg(price * discount) " "FROM foo.bar.repair_order_details" + ), + "mode": "published", + "name": "foo.bar.avg_repair_order_discounts", + }, + ), + ( + "/nodes/metric/", + { + "description": "Average time to dispatch a repair order", + "query": ( + "SELECT avg(dispatched_date - order_date) " "FROM foo.bar.repair_orders" + ), + "mode": "published", + "name": "foo.bar.avg_time_to_dispatch", + }, + ), + ( + ( + "/nodes/foo.bar.repair_order_details/columns/repair_order_id/" + "?dimension=foo.bar.repair_order&dimension_column=repair_order_id" + ), + {}, + ), + ( + ( + "/nodes/foo.bar.repair_orders/columns/municipality_id/" + "?dimension=foo.bar.municipality_dim&dimension_column=municipality_id" + ), + {}, + ), + ( + ( + "/nodes/foo.bar.repair_type/columns/contractor_id/" + "?dimension=foo.bar.contractor&dimension_column=contractor_id" + ), + {}, + ), + ( + ( + "/nodes/foo.bar.repair_orders/columns/hard_hat_id/" + "?dimension=foo.bar.hard_hat&dimension_column=hard_hat_id" + ), + {}, + ), + ( + ( + "/nodes/foo.bar.repair_orders/columns/dispatcher_id/" + "?dimension=foo.bar.dispatcher&dimension_column=dispatcher_id" + ), + {}, + ), + ( + ( + "/nodes/foo.bar.repair_order_details/columns/repair_order_id/" + "?dimension=foo.bar.repair_order&dimension_column=repair_order_id" + ), + {}, + ), + ( + ( + "/nodes/foo.bar.repair_order/columns/dispatcher_id/" + "?dimension=foo.bar.dispatcher&dimension_column=dispatcher_id" + ), + {}, + ), + ( + ( + "/nodes/foo.bar.repair_order/columns/hard_hat_id/" + "?dimension=foo.bar.hard_hat&dimension_column=hard_hat_id" + ), + {}, + ), + ( + ( + "/nodes/foo.bar.repair_order/columns/municipality_id/" + "?dimension=foo.bar.municipality_dim&dimension_column=municipality_id" + ), + {}, + ), + ( + "/nodes/cube/", + { + "description": "Cube #1 for metrics and dimensions.", + "mode": "published", + "name": "foo.bar.cube_one", + "metrics": ["foo.bar.num_repair_orders"], + "dimensions": ["foo.bar.municipality_dim.local_region"], + }, + ), + ( + "/nodes/cube/", + { + "description": "Cube #2 for metrics and dimensions.", + "mode": "published", + "name": "default.cube_two", + "metrics": ["default.num_repair_orders"], + "dimensions": ["default.municipality_dim.local_region"], + }, + ), + ( + "/nodes/transform/", + { + "description": "3 columns from default.repair_orders", + "query": ( + "SELECT repair_order_id, municipality_id, hard_hat_id " + "FROM default.repair_orders" + ), + "mode": "published", + "name": "default.repair_orders_thin", + }, + ), + ( + "/nodes/transform/", + { + "description": "3 columns from foo.bar.repair_orders", + "query": ( + "SELECT repair_order_id, municipality_id, hard_hat_id " + "FROM foo.bar.repair_orders" + ), + "mode": "published", + "name": "foo.bar.repair_orders_thin", + }, + ), +) + +COLUMN_MAPPINGS = { + "default.store.comments": [ + Column(name="id", type=IntegerType()), + Column(name="user_id", type=IntegerType()), + Column(name="timestamp", type=TimestampType()), + Column(name="text", type=StringType()), + ], +} + +QUERY_DATA_MAPPINGS: Dict[str, Union[DJException, QueryWithResults]] = { + """ + WITHm0_default_DOT_avg_repair_priceAS(SELECTdefault_DOT_hard_hat.city,\t + avg(default_DOT_repair_order_details.price)ASdefault_DOT_avg_repair_priceFROM + roads.repair_order_detailsASdefault_DOT_repair_order_detailsLEFTOUTERJOIN + (SELECTdefault_DOT_repair_orders.dispatcher_id,\tdefault_DOT_repair_orders.hard_hat_id, + \tdefault_DOT_repair_orders.municipality_id,\tdefault_DOT_repair_orders.repair_order_id + FROMroads.repair_ordersASdefault_DOT_repair_orders)ASdefault_DOT_repair_orderON + default_DOT_repair_order_details.repair_order_id=default_DOT_repair_order.repair_order_id + LEFTOUTERJOIN(SELECTdefault_DOT_hard_hats.city,\tdefault_DOT_hard_hats.hard_hat_id,\t + default_DOT_hard_hats.stateFROMroads.hard_hatsASdefault_DOT_hard_hats)AS + default_DOT_hard_hatONdefault_DOT_repair_order.hard_hat_id=default_DOT_hard_hat. + hard_hat_idGROUPBYdefault_DOT_hard_hat.city)SELECTm0_default_DOT_avg_repair_price. + default_DOT_avg_repair_price,\tm0_default_DOT_avg_repair_price.cityFROM + m0_default_DOT_avg_repair_price + """.strip() + .replace('"', "") + .replace("\n", "") + .replace(" ", ""): QueryWithResults( + **{ + "id": "bd98d6be-e2d2-413e-94c7-96d9411ddee2", + "submitted_query": "...", + "state": QueryState.FINISHED, + "results": [ + { + "columns": [ + {"name": "default_DOT_avg_repair_price", "type": "float"}, + {"name": "default_DOT_hard_hat_city", "type": "str"}, + ], + "rows": [ + (1.0, "Foo"), + (2.0, "Bar"), + ], + "sql": "", + }, + ], + "errors": [], + } + ), + """WITHm0_default_DOT_avg_repair_priceAS(SELECTdefault_DOT_hard_hat.state,\t + avg(default_DOT_repair_order_details.price)ASdefault_DOT_avg_repair_priceFROM + roads.repair_order_detailsASdefault_DOT_repair_order_detailsLEFTOUTERJOIN + (SELECTdefault_DOT_repair_orders.dispatcher_id,\tdefault_DOT_repair_orders.hard_hat_id, + \tdefault_DOT_repair_orders.municipality_id,\tdefault_DOT_repair_orders.repair_order_id + FROMroads.repair_ordersASdefault_DOT_repair_orders)ASdefault_DOT_repair_order + ONdefault_DOT_repair_order_details.repair_order_id=default_DOT_repair_order.repair_order_id + LEFTOUTERJOIN(SELECTdefault_DOT_hard_hats.hard_hat_id,\tdefault_DOT_hard_hats.state + FROMroads.hard_hatsASdefault_DOT_hard_hats)ASdefault_DOT_hard_hatON + default_DOT_repair_order.hard_hat_id=default_DOT_hard_hat.hard_hat_idGROUPBY + default_DOT_hard_hat.state)SELECTm0_default_DOT_avg_repair_price.default_DOT_avg_repair_price, + \tm0_default_DOT_avg_repair_price.stateFROMm0_default_DOT_avg_repair_price""".strip() + .replace('"', "") + .replace("\n", "") + .replace(" ", ""): QueryWithResults( + **{ + "id": "bd98d6be-e2d2-413e-94c7-96d9411ddee2", + "submitted_query": "...", + "state": QueryState.FINISHED, + "results": [], + "errors": [], + } + ), + """WITHm0_default_DOT_avg_repair_priceAS(SELECTdefault_DOT_hard_hat.postal_code, + \tavg(default_DOT_repair_order_details.price)ASdefault_DOT_avg_repair_priceFROM + roads.repair_order_detailsASdefault_DOT_repair_order_detailsLEFTOUTERJOIN + (SELECTdefault_DOT_repair_orders.dispatcher_id,\tdefault_DOT_repair_orders.hard_hat_id, + \tdefault_DOT_repair_orders.municipality_id,\tdefault_DOT_repair_orders.repair_order_id + FROMroads.repair_ordersASdefault_DOT_repair_orders)ASdefault_DOT_repair_orderON + default_DOT_repair_order_details.repair_order_id=default_DOT_repair_order.repair_order_id + LEFTOUTERJOIN(SELECTdefault_DOT_hard_hats.hard_hat_id,\tdefault_DOT_hard_hats.postal_code, + \tdefault_DOT_hard_hats.stateFROMroads.hard_hatsASdefault_DOT_hard_hats)ASdefault_DOT_hard_hat + ONdefault_DOT_repair_order.hard_hat_id=default_DOT_hard_hat.hard_hat_idGROUPBY + default_DOT_hard_hat.postal_code)SELECTm0_default_DOT_avg_repair_price. + default_DOT_avg_repair_price,\tm0_default_DOT_avg_repair_price.postal_code + FROMm0_default_DOT_avg_repair_price""".strip() + .replace('"', "") + .replace("\n", "") + .replace(" ", ""): ( + DJQueryServiceClientException("Error response from query service") + ), +} diff --git a/datajunction-clients/python/tests/test_admin.py b/datajunction-clients/python/tests/test_admin.py new file mode 100644 index 000000000..282d8c529 --- /dev/null +++ b/datajunction-clients/python/tests/test_admin.py @@ -0,0 +1,130 @@ +"""Tests DJ client""" +import pytest + +from datajunction import DJAdmin +from datajunction.exceptions import DJClientException + + +class TestDJAdmin: # pylint: disable=too-many-public-methods + """ + Tests for DJ client/builder functionality. + """ + + @pytest.fixture + def client(self, session_with_examples): + """ + Returns a DJ client instance + """ + return DJAdmin(requests_session=session_with_examples) # type: ignore + + # + # Data Catalogs + # + def test_get_catalog(self, client): + """ + Check that `client.get_catalog()` works as expected. + """ + result = client.get_catalog(name="default") + assert result == { + "name": "default", + "engines": [ + {"name": "spark", "version": "3.1.1", "uri": None, "dialect": "spark"}, + ], + } + + def test_add_catalog(self, client): + """ + Check that `client.add_catalog()` works as expected. + """ + # check not exists + result = client.get_catalog(name="foo-bar-baz") + assert "Catalog with name `foo-bar-baz` does not exist." in result["message"] + # add catalog + result = client.add_catalog(name="foo-bar-baz") + assert result is None + # check does exist + print(client.list_catalogs()) + result = client.get_catalog(name="foo-bar-baz") + assert result == { + "name": "foo-bar-baz", + "engines": [], + } + + # + # Database Engines + # + def test_get_engine(self, client): + """ + Check that `client.get_engine()` works as expected. + """ + result = client.get_engine(name="spark", version="3.1.1") + assert result == { + "dialect": "spark", + "name": "spark", + "uri": None, + "version": "3.1.1", + } + + def test_add_engine(self, client): + """ + Check that `client.get_engine()` works as expected. + """ + # check not exist + result = client.get_engine(name="8-cylinder", version="7.11") + assert "Engine not found: `8-cylinder` version `7.11`" in result["detail"] + # add engine + client.add_engine( + name="8-cylinder", + version="7.11", + uri="go/to-the-corner", + dialect="trino", + ) + # check not exist + result = client.get_engine(name="8-cylinder", version="7.11") + assert result == { + "dialect": "trino", + "name": "8-cylinder", + "uri": "go/to-the-corner", + "version": "7.11", + } + + def test_link_engine_to_catalog(self, client): + """ + Check that `client.get_engine()` works as expected. + """ + # try to link engine to catalog + with pytest.raises(DJClientException) as exc: + result = client.link_engine_to_catalog( + engine="8-cylinder", + version="7.11", + catalog="public", + ) + assert "Engine not found: `8-cylinder` version `7.11`" in str(exc) + # add engine + client.add_engine( + name="8-cylinder", + version="7.11", + uri="go/to-the-corner", + dialect="trino", + ) + # try to link engine to catalog (again) + result = client.link_engine_to_catalog( + engine="8-cylinder", + version="7.11", + catalog="public", + ) + assert result is None + # check the catalog + result = client.get_catalog(name="public") + assert result == { + "engines": [ + {"dialect": None, "name": "postgres", "uri": None, "version": "15.2"}, + { + "dialect": "trino", + "name": "8-cylinder", + "uri": "go/to-the-corner", + "version": "7.11", + }, + ], + "name": "public", + } diff --git a/datajunction-clients/python/tests/test_builder.py b/datajunction-clients/python/tests/test_builder.py new file mode 100644 index 000000000..52bf466a1 --- /dev/null +++ b/datajunction-clients/python/tests/test_builder.py @@ -0,0 +1,745 @@ +"""Tests DJ client""" +import pytest + +from datajunction import DJBuilder +from datajunction.exceptions import ( + DJClientException, + DJNamespaceAlreadyExists, + DJNodeAlreadyExists, +) +from datajunction.models import ( + AvailabilityState, + Column, + ColumnAttribute, + Engine, + MaterializationConfig, + NodeMode, +) + + +class TestDJBuilder: # pylint: disable=too-many-public-methods + """ + Tests for DJ client/builder functionality. + """ + + @pytest.fixture + def client(self, session_with_examples): + """ + Returns a DJ client instance + """ + return DJBuilder(requests_session=session_with_examples) # type: ignore + + def test_nodes_in_namespace(self, client): + """ + Check that `client._get_nodes_in_namespace()` works as expected. + """ + assert set(client.namespace("foo.bar").nodes()) == { + "foo.bar.repair_orders", + "foo.bar.repair_order_details", + "foo.bar.repair_type", + "foo.bar.contractors", + "foo.bar.municipality_municipality_type", + "foo.bar.municipality_type", + "foo.bar.municipality", + "foo.bar.dispatchers", + "foo.bar.hard_hats", + "foo.bar.hard_hat_state", + "foo.bar.us_states", + "foo.bar.us_region", + "foo.bar.repair_order", + "foo.bar.contractor", + "foo.bar.hard_hat", + "foo.bar.local_hard_hats", + "foo.bar.us_state", + "foo.bar.dispatcher", + "foo.bar.municipality_dim", + "foo.bar.num_repair_orders", + "foo.bar.avg_repair_price", + "foo.bar.total_repair_cost", + "foo.bar.avg_length_of_employment", + "foo.bar.total_repair_order_discounts", + "foo.bar.avg_repair_order_discounts", + "foo.bar.avg_time_to_dispatch", + "foo.bar.cube_one", + "foo.bar.repair_orders_thin", + } + assert set(client.namespace("foo.bar").sources()) == { + "foo.bar.repair_orders", + "foo.bar.repair_order_details", + "foo.bar.repair_type", + "foo.bar.contractors", + "foo.bar.municipality_municipality_type", + "foo.bar.municipality_type", + "foo.bar.municipality", + "foo.bar.dispatchers", + "foo.bar.hard_hats", + "foo.bar.hard_hat_state", + "foo.bar.us_states", + "foo.bar.us_region", + } + assert set(client.list_dimensions(namespace="foo.bar")) == { + "foo.bar.repair_order", + "foo.bar.contractor", + "foo.bar.hard_hat", + "foo.bar.local_hard_hats", + "foo.bar.us_state", + "foo.bar.dispatcher", + "foo.bar.municipality_dim", + } + assert set(client.list_metrics(namespace="foo.bar")) == { + "foo.bar.num_repair_orders", + "foo.bar.avg_repair_price", + "foo.bar.total_repair_cost", + "foo.bar.avg_length_of_employment", + "foo.bar.total_repair_order_discounts", + "foo.bar.avg_repair_order_discounts", + "foo.bar.avg_time_to_dispatch", + } + assert client.namespace("foo.bar").transforms() == [ + "foo.bar.repair_orders_thin", + ] + assert client.namespace("foo.bar").cubes() == ["foo.bar.cube_one"] + + def test_all_nodes(self, client): + """ + Verifies that retrieving nodes with `client.nodes()` or node-type + specific calls like `client.sources()` work. + """ + expected_names_only = { + "default.repair_orders", + "default.repair_order_details", + "default.repair_type", + "default.contractors", + "default.municipality_municipality_type", + "default.municipality_type", + "default.municipality", + "default.dispatchers", + "default.hard_hats", + "default.hard_hat_state", + "default.us_states", + "default.us_region", + "default.repair_order", + "default.contractor", + "default.hard_hat", + "default.local_hard_hats", + "default.us_state", + "default.dispatcher", + "default.municipality_dim", + "default.num_repair_orders", + "default.avg_repair_price", + "default.total_repair_cost", + "default.avg_length_of_employment", + "default.total_repair_order_discounts", + "default.avg_repair_order_discounts", + "default.avg_time_to_dispatch", + "default.cube_two", + "default.repair_orders_thin", + } + result_names_only = client.namespace("default").nodes() + assert set(result_names_only) == expected_names_only + + # sources + result_names_only = client.namespace("default").sources() + assert set(result_names_only) == { + "default.repair_orders", + "default.repair_order_details", + "default.repair_type", + "default.contractors", + "default.municipality_municipality_type", + "default.municipality_type", + "default.municipality", + "default.dispatchers", + "default.hard_hats", + "default.hard_hat_state", + "default.us_states", + "default.us_region", + } + + repair_orders = client.source("default.repair_orders") + assert repair_orders.name == "default.repair_orders" + assert repair_orders.catalog == "default" + assert repair_orders.schema_ == "roads" + assert repair_orders.table == "repair_orders" + assert repair_orders.type == "source" + + # dimensions (all) + all_dimensions = client.list_dimensions() + assert set(all_dimensions) == { + "default.repair_order", + "default.contractor", + "default.hard_hat", + "default.local_hard_hats", + "default.us_state", + "default.dispatcher", + "default.municipality_dim", + "foo.bar.repair_order", + "foo.bar.contractor", + "foo.bar.hard_hat", + "foo.bar.local_hard_hats", + "foo.bar.us_state", + "foo.bar.dispatcher", + "foo.bar.municipality_dim", + } + # dimensions (namespace: default) + result_names_only = client.list_dimensions(namespace="default") + assert set(result_names_only) == { + "default.repair_order", + "default.contractor", + "default.hard_hat", + "default.local_hard_hats", + "default.us_state", + "default.dispatcher", + "default.municipality_dim", + } + repair_order_dim = client.dimension("default.repair_order") + assert repair_order_dim.name == "default.repair_order" + assert "FROM default.repair_orders" in repair_order_dim.query + assert repair_order_dim.type == "dimension" + assert repair_order_dim.primary_key == ["repair_order_id"] + + # transforms + result = client.namespace("default").transforms() + assert result == ["default.repair_orders_thin"] + + # metrics + result_names_only = client.list_metrics(namespace="default") + assert set(result_names_only) == { + "default.num_repair_orders", + "default.avg_repair_price", + "default.total_repair_cost", + "default.avg_length_of_employment", + "default.total_repair_order_discounts", + "default.avg_repair_order_discounts", + "default.avg_time_to_dispatch", + } + + num_repair_orders = client.metric("default.num_repair_orders") + assert num_repair_orders.name == "default.num_repair_orders" + assert num_repair_orders.query == ( + "SELECT count(repair_order_id) default_DOT_num_repair_orders " + "\n FROM default.repair_orders\n\n" + ) + assert num_repair_orders.type == "metric" + + # cubes + result = client.namespace("default").cubes() + assert result == ["default.cube_two"] + with pytest.raises(DJClientException) as exc_info: + client.cube("a_cube") + assert "Cube `a_cube` does not exist" in str(exc_info) + + def test_deactivating_node(self, client): # pylint: disable=unused-argument + """ + Verifies that deactivating and reactivating a node works. + """ + length_metric = client.metric("default.avg_length_of_employment") + response = length_metric.delete() + assert response is None + assert "default.avg_length_of_employment" not in client.list_metrics( + namespace="default", + ) + response = length_metric.restore() + assert response is None + assert "default.avg_length_of_employment" in client.list_metrics( + namespace="default", + ) + + def test_register_table(self, client): # pylint: disable=unused-argument + """ + Verifies that registering a table works. + """ + client.create_namespace("source") + store_comments = client.register_table( + catalog="default", + schema="store", + table="comments", + ) + assert store_comments.name == "source.default.store.comments" + assert "source.default.store.comments" in client.namespace("source").sources() + + def test_create_and_update_node(self, client): # pylint: disable=unused-argument + """ + Verifies that creating nodes works. + """ + # create it + account_type_table = client.create_source( + name="default.account_type_table", + description="A source table for account type data", + display_name="Default: Account Type Table", + catalog="default", + schema_="store", + table="account_type_table", + columns=[ + Column(name="id", type="int"), + Column(name="account_type_name", type="string"), + Column(name="account_type_classification", type="int"), + Column(name="preferred_payment_method", type="int"), + ], + mode=NodeMode.DRAFT, + ) + assert account_type_table.name == "default.account_type_table" + assert "default.account_type_table" in client.namespace("default").sources() + + # update it + account_type_table = client.source(node_name="default.account_type_table") + account_type_table.save(mode=NodeMode.PUBLISHED) + + def test_create_nodes(self, client): # pylint: disable=unused-argument + """ + Verifies that creating nodes works. + """ + # source nodes + account_type_table = client.create_source( + name="default.account_type_table", + description="A source table for account type data", + display_name="Default: Account Type Table", + catalog="default", + schema_="store", + table="account_type_table", + columns=[ + Column(name="id", type="int"), + Column(name="account_type_name", type="string"), + Column(name="account_type_classification", type="int"), + Column(name="preferred_payment_method", type="int"), + ], + mode=NodeMode.PUBLISHED, + ) + assert account_type_table.name == "default.account_type_table" + assert "default.account_type_table" in client.namespace("default").sources() + + revenue = client.create_source( + name="default.revenue", + description="Record of payments", + display_name="Default: Payment Records", + catalog="default", + schema_="accounting", + table="revenue", + columns=[ + Column(name="payment_id", type="int"), + Column(name="payment_amount", type="float"), + Column(name="payment_type", type="int"), + Column(name="customer_id", type="int"), + Column(name="account_type", type="string"), + ], + mode=NodeMode.PUBLISHED, + ) + assert revenue.name == "default.revenue" + assert "default.revenue" in client.namespace("default").sources() + + # make sure we get a failure if we try to create same node again + with pytest.raises(DJNodeAlreadyExists) as exc_info: + revenue = client.create_source( + name="default.revenue", + description="Record of payments", + display_name="Default: Payment Records", + catalog="default", + schema_="accounting", + table="revenue", + columns=[ + Column(name="payment_id", type="int"), + Column(name="payment_amount", type="float"), + Column(name="payment_type", type="int"), + Column(name="customer_id", type="int"), + Column(name="account_type", type="string"), + ], + mode=NodeMode.PUBLISHED, + ) + assert "Node `default.revenue` already exists." in str(exc_info.value) + + # dimension nodes + payment_type_dim = client.create_dimension( + name="default.payment_type", + description="Payment type dimension", + display_name="Default: Payment Type", + query=( + "SELECT id, payment_type_name, payment_type_classification " + "FROM default.payment_type_table" + ), + primary_key=["id"], + mode=NodeMode.DRAFT, + ) + payment_type_dim._validate() # pylint: disable=protected-access + assert payment_type_dim.name == "default.payment_type" + assert "default.payment_type" in client.list_dimensions(namespace="default") + payment_type_dim.publish() # Test changing a draft node to published + payment_type_dim.refresh() + assert payment_type_dim.mode == NodeMode.PUBLISHED + + account_type_dim = client.create_dimension( + name="default.account_type", + description="Account type dimension", + display_name="Default: Account Type", + query=( + "SELECT id, account_type_name, " + "account_type_classification FROM " + "default.account_type_table" + ), + primary_key=["id"], + mode=NodeMode.PUBLISHED, + ) + assert account_type_dim.name == "default.account_type" + assert "default.account_type" in client.list_dimensions(namespace="default") + + # transform nodes + large_revenue_payments_only = client.create_transform( + name="default.large_revenue_payments_only", + description="Default: Only large revenue payments", + query=( + "SELECT payment_id, payment_amount, customer_id, account_type " + "FROM default.revenue WHERE payment_amount > 1000000" + ), + mode=NodeMode.PUBLISHED, + ) + assert large_revenue_payments_only.name == "default.large_revenue_payments_only" + assert ( + "default.large_revenue_payments_only" + in client.namespace("default").transforms() + ) + client.transform("default.large_revenue_payments_only") + + result = large_revenue_payments_only.add_materialization( + MaterializationConfig( + engine=Engine(name="spark", version="3.1.1"), + schedule="0 * * * *", + config={}, + ), + ) + assert result == { + "message": "Successfully updated materialization config named `default` for " + "node `default.large_revenue_payments_only`", + "urls": [["http://fake.url/job"]], + } + + result = large_revenue_payments_only.deactivate_materialization( + materialization_name="default", + ) + assert result == { + "message": "The materialization named `default` on node " + "`default.large_revenue_payments_only` has been successfully deactivated", + } + + large_revenue_payments_and_business_only = client.create_transform( + name="default.large_revenue_payments_and_business_only", + description="Only large revenue payments from business accounts", + query=( + "SELECT payment_id, payment_amount, customer_id, account_type " + "FROM default.revenue WHERE " + "default.large_revenue_payments_and_business_only > 1000000 " + "AND account_type='BUSINESS'" + ), + mode=NodeMode.PUBLISHED, + ) + assert ( + large_revenue_payments_and_business_only.name + == "default.large_revenue_payments_and_business_only" + ) + assert ( + "default.large_revenue_payments_and_business_only" + in client.namespace( + "default", + ).transforms() + ) + + # metric nodes + number_of_account_types = client.create_metric( + name="default.number_of_account_types", + description="Total number of account types", + query="SELECT count(id) FROM default.account_type", + mode=NodeMode.PUBLISHED, + ) + assert number_of_account_types.name == "default.number_of_account_types" + assert "default.number_of_account_types" in client.list_metrics( + namespace="default", + ) + + # cube nodes + cube_one = client.create_cube( + name="default.cube_one", + description="Ice ice cube.", + metrics=["default.number_of_account_types"], + dimensions=["default.payment_type"], + mode=NodeMode.PUBLISHED, + ) + assert cube_one.name == "default.cube_one" + + def test_link_unlink_dimension(self, client): # pylint: disable=unused-argument + """ + Check that linking and unlinking dimensions to a node's column works + """ + repair_type = client.source("foo.bar.repair_type") + result = repair_type.link_dimension( + "contractor_id", + "foo.bar.contractor", + "contractor_id", + ) + assert result["message"] == ( + "Dimension node foo.bar.contractor has been successfully linked to " + "column contractor_id on node foo.bar.repair_type" + ) + + # Unlink the dimension + result = repair_type.unlink_dimension( + "contractor_id", + "foo.bar.contractor", + "contractor_id", + ) + assert result["message"] == ( + "The dimension link on the node foo.bar.repair_type's contractor_id to " + "foo.bar.contractor has been successfully removed." + ) + + def test_sql(self, client): # pylint: disable=unused-argument + """ + Check that getting sql via the client works as expected. + """ + result = client.sql(metrics=["foo.bar.avg_repair_price"]) + assert "SELECT" in result and "FROM" in result + + # Retrieve SQL for a single metric + result = client.sql( + metrics=["foo.bar.avg_repair_price"], + dimensions=["dimension_that_does_not_exist"], + filters=[], + ) + assert ( + result["message"] + == "Cannot resolve type of column dimension_that_does_not_exist in SELECT " + "dimension_that_does_not_exist,\n" + "\tavg(foo_DOT_bar_DOT_repair_order_details.price) " + "foo_DOT_bar_DOT_avg_repair_price \n" + " FROM roads.repair_order_details AS foo_DOT_bar_DOT_repair_order_details \n" + " GROUP BY dimension_that_does_not_exist\n" + ) + + # Retrieve SQL for multiple metrics using the client object + result = client.sql( + metrics=["default.total_repair_cost", "default.avg_repair_price"], + dimensions=[ + "default.hard_hat.city", + "default.hard_hat.state", + "default.dispatcher.company_name", + ], + filters=["default.hard_hat.state = 'AZ'"], + engine_name="spark", + engine_version="3.1.1", + ) + assert "SELECT" in result and "FROM" in result + + # Should fail due to dimension not being available + result = client.sql( + metrics=["foo.bar.num_repair_orders", "foo.bar.avg_repair_price"], + dimensions=["default.hard_hat.city"], + filters=["default.hard_hat.state = 'AZ'"], + engine_name="spark", + engine_version="3.1.1", + ) + assert result["message"] == ( + "The dimension attribute `default.hard_hat.city` is not available on " + "every metric and thus cannot be included." + ) + + def test_get_dimensions(self, client): + """ + Check that `metric.dimensions()` works as expected. + """ + metric = client.metric(node_name="foo.bar.avg_repair_price") + result = metric.dimensions() + assert { + "name": "foo.bar.dispatcher.company_name", + "type": "string", + "path": [ + "foo.bar.repair_order_details.repair_order_id", + "foo.bar.repair_order.dispatcher_id", + ], + } in result + + def test_failure_modes(self, client): + """ + Test some client failure modes when retrieving nodes. + """ + with pytest.raises(DJClientException) as excinfo: + client.transform(node_name="default.fruit") + assert "No node with name default.fruit exists!" in str(excinfo) + + with pytest.raises(DJClientException) as excinfo: + client.transform(node_name="foo.bar.avg_repair_price") + assert ( + "A node with name foo.bar.avg_repair_price exists, but it is not a transform node!" + in str( + excinfo, + ) + ) + + def test_create_namespace(self, client): + """ + Verifies that creating a new namespace works. + """ + with pytest.raises(DJClientException) as exc_info: + client.namespace(namespace="roads.demo") + assert "Namespace `roads.demo` does not exist" in str(exc_info.value) + + namespace = client.create_namespace(namespace="roads.demo") + assert namespace.namespace == "roads.demo" + + with pytest.raises(DJNamespaceAlreadyExists) as exc_info: + client.create_namespace(namespace="roads.demo") + assert "Node namespace `roads.demo` already exists" in str(exc_info.value) + + def test_create_delete_restore_namespace(self, client): + """ + Verifies that deleting a new namespace works. + """ + # create it first + namespace = client.create_namespace(namespace="roads.demo") + assert namespace.namespace == "roads.demo" + with pytest.raises(DJNamespaceAlreadyExists) as exc_info: + client.create_namespace(namespace="roads.demo") + assert "Node namespace `roads.demo` already exists" in str(exc_info.value) + + # then delete it + response = client.delete_namespace(namespace="roads.demo") + assert response is None + with pytest.raises(DJClientException) as exc_info: + client.delete_namespace(namespace="roads.demo") + assert "Namespace `roads.demo` is already deactivated." in str(exc_info.value) + + # and then restore it + response = client.restore_namespace(namespace="roads.demo") + assert response is None + with pytest.raises(DJClientException) as exc_info: + client.restore_namespace(namespace="roads.demo") + assert "Node namespace `roads.demo` already exists and is active." in str( + exc_info.value, + ) + + def test_get_node_revisions(self, client): + """ + Verifies that retrieving node revisions works + """ + local_hard_hats = client.dimension("default.local_hard_hats") + local_hard_hats.display_name = "local hard hats" + local_hard_hats.description = "Local hard hats dimension" + local_hard_hats.save() + local_hard_hats.primary_key = ["hard_hat_id", "last_name"] + local_hard_hats.save() + revs = local_hard_hats.list_revisions() + assert len(revs) == 3 + assert [rev["version"] for rev in revs] == ["v1.0", "v1.1", "v2.0"] + + def test_update_node_with_query(self, client): + """ + Verify that updating a node with a query works + """ + local_hard_hats = client.dimension("default.local_hard_hats") + local_hard_hats.query = """ + SELECT + hh.hard_hat_id, + last_name, + first_name, + title, + birth_date, + hire_date, + address, + city, + state, + postal_code, + country, + manager, + contractor_id, + hhs.state_id AS state_id + FROM default.hard_hats hh + LEFT JOIN default.hard_hat_state hhs + ON hh.hard_hat_id = hhs.hard_hat_id + WHERE hh.state_id = 'CA' + """ + response = local_hard_hats.save() + assert "WHERE hh.state_id = 'CA'" in response["query"] + assert response["version"] == "v2.0" + + local_hard_hats.display_name = "local hard hats" + local_hard_hats.description = "Local hard hats dimension" + response = local_hard_hats.save() + assert response["display_name"] == "local hard hats" + assert response["description"] == "Local hard hats dimension" + assert response["version"] == "v2.1" + + local_hard_hats.primary_key = ["hard_hat_id", "last_name"] + response = local_hard_hats.save() + + assert response["version"] == "v3.0" + assert { + "name": "hard_hat_id", + "type": "int", + "attributes": [ + {"attribute_type": {"namespace": "system", "name": "primary_key"}}, + ], + "dimension": None, + } in response["columns"] + assert { + "name": "last_name", + "type": "string", + "attributes": [ + {"attribute_type": {"namespace": "system", "name": "primary_key"}}, + ], + "dimension": None, + } in response["columns"] + + def test_update_source_node(self, client): + """ + Verify that updating a source node's columns works + """ + us_states = client.source("default.us_states") + new_columns = [ + {"name": "state_id", "type": "int"}, + {"name": "name", "type": "string"}, + {"name": "abbr", "type": "string"}, + {"name": "region", "type": "int"}, + ] + us_states.columns = new_columns + response = us_states.save() + assert response["columns"] == [ + {"attributes": [], "dimension": None, "name": "state_id", "type": "int"}, + {"attributes": [], "dimension": None, "name": "name", "type": "string"}, + {"attributes": [], "dimension": None, "name": "abbr", "type": "string"}, + {"attributes": [], "dimension": None, "name": "region", "type": "int"}, + ] + assert response["version"] == "v2.0" + + def test_add_availability(self, client): + """ + Verify adding an availability state to a node + """ + dim = client.dimension(node_name="default.contractor") + response = dim.add_availability( + AvailabilityState( + catalog="default", + schema_="materialized", + table="contractor", + valid_through_ts=1688660209, + ), + ) + assert response == {"message": "Availability state successfully posted"} + + def test_set_column_attributes(self, client): + """ + Verify setting column attributes on a node + """ + dim = client.source(node_name="default.contractors") + response = dim.set_column_attributes( + [ + ColumnAttribute( + attribute_type_name="dimension", + column_name="contact_title", + ), + ], + ) + assert response == [ + { + "attributes": [ + {"attribute_type": {"name": "dimension", "namespace": "system"}}, + ], + "dimension": None, + "name": "contact_title", + "type": "string", + }, + ] diff --git a/datajunction-clients/python/tests/test_client.py b/datajunction-clients/python/tests/test_client.py new file mode 100644 index 000000000..d50cc5731 --- /dev/null +++ b/datajunction-clients/python/tests/test_client.py @@ -0,0 +1,339 @@ +"""Tests DJ client""" +import pandas +import pytest + +from datajunction import DJClient +from datajunction.exceptions import DJClientException + + +class TestDJClient: # pylint: disable=too-many-public-methods + """ + Tests for DJ client functionality. + """ + + @pytest.fixture + def client(self, session_with_examples): + """ + Returns a DJ client instance + """ + return DJClient(requests_session=session_with_examples) # type: ignore + + # + # List basic objects: namespaces, dimensions, metrics, cubes + # + def test_list_namespaces(self, client): + """ + Check that `client.list_namespaces()` works as expected. + """ + # full list + expected = ["default", "foo.bar"] + result = client.list_namespaces() + assert result == expected + + # partial list + partial = ["foo.bar"] + result = client.list_namespaces(prefix="foo") + assert result == partial + + def test_list_dimensions(self, client): + """ + Check that `client.list_dimensions()` works as expected. + """ + # full list + dims = client.list_dimensions() + assert dims == [ + "default.repair_order", + "default.contractor", + "default.hard_hat", + "default.local_hard_hats", + "default.us_state", + "default.dispatcher", + "default.municipality_dim", + "foo.bar.repair_order", + "foo.bar.contractor", + "foo.bar.hard_hat", + "foo.bar.local_hard_hats", + "foo.bar.us_state", + "foo.bar.dispatcher", + "foo.bar.municipality_dim", + ] + + # partial list + dims = client.list_dimensions(namespace="foo.bar") + assert dims == [ + "foo.bar.repair_order", + "foo.bar.contractor", + "foo.bar.hard_hat", + "foo.bar.local_hard_hats", + "foo.bar.us_state", + "foo.bar.dispatcher", + "foo.bar.municipality_dim", + ] + + def test_list_metrics(self, client): + """ + Check that `client.list_metrics()` works as expected. + """ + # full list + metrics = client.list_metrics() + assert metrics == [ + "default.num_repair_orders", + "default.avg_repair_price", + "default.total_repair_cost", + "default.avg_length_of_employment", + "default.total_repair_order_discounts", + "default.avg_repair_order_discounts", + "default.avg_time_to_dispatch", + "foo.bar.num_repair_orders", + "foo.bar.avg_repair_price", + "foo.bar.total_repair_cost", + "foo.bar.avg_length_of_employment", + "foo.bar.total_repair_order_discounts", + "foo.bar.avg_repair_order_discounts", + "foo.bar.avg_time_to_dispatch", + ] + + # partial list + metrics = client.list_metrics(namespace="foo.bar") + assert metrics == [ + "foo.bar.num_repair_orders", + "foo.bar.avg_repair_price", + "foo.bar.total_repair_cost", + "foo.bar.avg_length_of_employment", + "foo.bar.total_repair_order_discounts", + "foo.bar.avg_repair_order_discounts", + "foo.bar.avg_time_to_dispatch", + ] + + def test_list_cubes(self, client): + """ + Check that `client.list_cubes()` works as expected. + """ + # full list + cubes = client.list_cubes() + assert cubes == ["foo.bar.cube_one", "default.cube_two"] + + # partial list + cubes = client.list_cubes(namespace="foo.bar") + assert cubes == ["foo.bar.cube_one"] + + # + # List other nodes: sources, transforms, all. + # + def test_list_sources(self, client): + """ + Check that `client.list_sources()` works as expected. + """ + # full list + nodes = client.list_sources() + assert nodes == [ + "default.repair_orders", + "default.repair_order_details", + "default.repair_type", + "default.contractors", + "default.municipality_municipality_type", + "default.municipality_type", + "default.municipality", + "default.dispatchers", + "default.hard_hats", + "default.hard_hat_state", + "default.us_states", + "default.us_region", + "foo.bar.repair_orders", + "foo.bar.repair_order_details", + "foo.bar.repair_type", + "foo.bar.contractors", + "foo.bar.municipality_municipality_type", + "foo.bar.municipality_type", + "foo.bar.municipality", + "foo.bar.dispatchers", + "foo.bar.hard_hats", + "foo.bar.hard_hat_state", + "foo.bar.us_states", + "foo.bar.us_region", + ] + + # partial list + nodes = client.list_sources(namespace="foo.bar") + assert nodes == [ + "foo.bar.repair_orders", + "foo.bar.repair_order_details", + "foo.bar.repair_type", + "foo.bar.contractors", + "foo.bar.municipality_municipality_type", + "foo.bar.municipality_type", + "foo.bar.municipality", + "foo.bar.dispatchers", + "foo.bar.hard_hats", + "foo.bar.hard_hat_state", + "foo.bar.us_states", + "foo.bar.us_region", + ] + + def test_list_transforms(self, client): + """ + Check that `client.list_transforms)()` works as expected. + """ + # full list + nodes = client.list_transforms() + assert nodes == ["default.repair_orders_thin", "foo.bar.repair_orders_thin"] + + # partial list + nodes = client.list_transforms(namespace="foo.bar") + assert nodes == ["foo.bar.repair_orders_thin"] + + def test_list_nodes(self, client): + """ + Check that `client.list_nodes)()` works as expected. + """ + # full list + nodes = client.list_nodes() + assert nodes == [ + "default.repair_orders", + "default.repair_order_details", + "default.repair_type", + "default.contractors", + "default.municipality_municipality_type", + "default.municipality_type", + "default.municipality", + "default.dispatchers", + "default.hard_hats", + "default.hard_hat_state", + "default.us_states", + "default.us_region", + "default.repair_order", + "default.contractor", + "default.hard_hat", + "default.local_hard_hats", + "default.us_state", + "default.dispatcher", + "default.municipality_dim", + "default.num_repair_orders", + "default.avg_repair_price", + "default.total_repair_cost", + "default.avg_length_of_employment", + "default.total_repair_order_discounts", + "default.avg_repair_order_discounts", + "default.avg_time_to_dispatch", + "foo.bar.repair_orders", + "foo.bar.repair_order_details", + "foo.bar.repair_type", + "foo.bar.contractors", + "foo.bar.municipality_municipality_type", + "foo.bar.municipality_type", + "foo.bar.municipality", + "foo.bar.dispatchers", + "foo.bar.hard_hats", + "foo.bar.hard_hat_state", + "foo.bar.us_states", + "foo.bar.us_region", + "foo.bar.repair_order", + "foo.bar.contractor", + "foo.bar.hard_hat", + "foo.bar.local_hard_hats", + "foo.bar.us_state", + "foo.bar.dispatcher", + "foo.bar.municipality_dim", + "foo.bar.num_repair_orders", + "foo.bar.avg_repair_price", + "foo.bar.total_repair_cost", + "foo.bar.avg_length_of_employment", + "foo.bar.total_repair_order_discounts", + "foo.bar.avg_repair_order_discounts", + "foo.bar.avg_time_to_dispatch", + "foo.bar.cube_one", + "default.cube_two", + "default.repair_orders_thin", + "foo.bar.repair_orders_thin", + ] + + # partial list + nodes = client.list_nodes(namespace="foo.bar") + assert nodes == [ + "foo.bar.repair_orders", + "foo.bar.repair_order_details", + "foo.bar.repair_type", + "foo.bar.contractors", + "foo.bar.municipality_municipality_type", + "foo.bar.municipality_type", + "foo.bar.municipality", + "foo.bar.dispatchers", + "foo.bar.hard_hats", + "foo.bar.hard_hat_state", + "foo.bar.us_states", + "foo.bar.us_region", + "foo.bar.repair_order", + "foo.bar.contractor", + "foo.bar.hard_hat", + "foo.bar.local_hard_hats", + "foo.bar.us_state", + "foo.bar.dispatcher", + "foo.bar.municipality_dim", + "foo.bar.num_repair_orders", + "foo.bar.avg_repair_price", + "foo.bar.total_repair_cost", + "foo.bar.avg_length_of_employment", + "foo.bar.total_repair_order_discounts", + "foo.bar.avg_repair_order_discounts", + "foo.bar.avg_time_to_dispatch", + "foo.bar.cube_one", + "foo.bar.repair_orders_thin", + ] + + # + # Get common metrics and dimensions + # + + # + # SQL and data + # + def test_data(self, client): + """ + Test data retreval for a metric and dimension(s) + """ + # Retrieve data for a single metric + result = client.data( + metrics=["default.avg_repair_price"], + dimensions=["default.hard_hat.city"], + ) + + expected_df = pandas.DataFrame.from_dict( + {"default_DOT_avg_repair_price": [1.0, 2.0], "city": ["Foo", "Bar"]}, + ) + pandas.testing.assert_frame_equal(result, expected_df) + + # No data + with pytest.raises(DJClientException) as exc_info: + client.data( + metrics=["default.avg_repair_price"], + dimensions=["default.hard_hat.state"], + ) + assert "No data for query!" in str(exc_info) + + # Error propagation + with pytest.raises(DJClientException) as exc_info: + client.data( + metrics=["default.avg_repair_price"], + dimensions=["default.hard_hat.postal_code"], + ) + assert "Error response from query service" in str(exc_info) + + # + # Data Catalog and Engines + # + def test_list_catalogs(self, client): + """ + Check that `client.list_catalogs()` works as expected. + """ + result = client.list_catalogs() + assert result == ["draft", "default", "public"] + + def test_list_engines(self, client): + """ + Check that `client.list_engines()` works as expected. + """ + result = client.list_engines() + assert result == [ + {"name": "spark", "version": "3.1.1"}, + {"name": "postgres", "version": "15.2"}, + ] diff --git a/datajunction-clients/python/tests/test_integration.py b/datajunction-clients/python/tests/test_integration.py new file mode 100644 index 000000000..e54f63aac --- /dev/null +++ b/datajunction-clients/python/tests/test_integration.py @@ -0,0 +1,739 @@ +""" +Integration tests to be run against the latest full demo datajunction environment +""" +# pylint: disable=too-many-lines,line-too-long,protected-access +import namesgenerator +import pandas +import pytest + +from datajunction import DJBuilder +from datajunction.exceptions import DJClientException +from datajunction.models import AvailabilityState, ColumnAttribute, NodeMode, NodeStatus + + +@pytest.mark.skipif("not config.getoption('integration')") +def test_integration(): # pylint: disable=too-many-statements,too-many-locals,line-too-long + """ + Integration test + """ + dj = DJBuilder() # pylint: disable=invalid-name,line-too-long + + # Create a namespace + namespace = f"integration.python.{namesgenerator.get_random_name()}" + dj.create_namespace(namespace) + + # List namespaces + matching_namespace = None + for existing_namespace in dj.list_namespaces(): + if existing_namespace == namespace: + matching_namespace = existing_namespace + assert matching_namespace + + # Create a source + dj.create_source( + name=f"{namespace}.repair_orders", + description="Repair orders", + catalog="warehouse", + schema_="roads", + table="repair_orders", + columns=[ + {"name": "repair_order_id", "type": "int"}, + {"name": "municipality_id", "type": "string"}, + {"name": "hard_hat_id", "type": "int"}, + {"name": "order_date", "type": "timestamp"}, + {"name": "required_date", "type": "timestamp"}, + {"name": "dispatched_date", "type": "timestamp"}, + {"name": "dispatcher_id", "type": "int"}, + ], + ) + + # Get source + dj.source(f"{namespace}.repair_orders") + + # Create a transform + dj.create_transform( + name=f"{namespace}.repair_orders_w_dispatchers", + description="Repair orders that have a dispatcher", + query=f""" + SELECT + repair_order_id, + municipality_id, + hard_hat_id, + dispatcher_id + FROM {namespace}.repair_orders + WHERE dispatcher_id IS NOT NULL + """, + ) + + # Get transform + dj.transform(f"{namespace}.repair_orders_w_dispatchers") + + # Create a source and dimension node + dj.create_source( + name=f"{namespace}.dispatchers", + description="Different third party dispatcher companies that coordinate repairs", + catalog="warehouse", + schema_="roads", + table="dispatchers", + columns=[ + {"name": "dispatcher_id", "type": "int"}, + {"name": "company_name", "type": "string"}, + {"name": "phone", "type": "string"}, + ], + ) + dj.create_dimension( + name=f"{namespace}.all_dispatchers", + description="All dispatchers", + primary_key=["dispatcher_id"], + query=f""" + SELECT + dispatcher_id, + company_name, + phone + FROM {namespace}.dispatchers + """, + ) + + # Get dimension + dj.dimension(f"{namespace}.all_dispatchers") + + # Create metrics + dj.create_metric( + name=f"{namespace}.num_repair_orders", + description="Number of repair orders", + query=f"SELECT count(repair_order_id) FROM {namespace}.repair_orders", + ) + + # List metrics + assert f"{namespace}.num_repair_orders" in dj.list_metrics() + + # Create a dimension link + source = dj.source(f"{namespace}.repair_orders") + source.link_dimension( + column="dispatcher_id", + dimension=f"{namespace}.all_dispatchers", + dimension_column="dispatcher_id", + ) + source.unlink_dimension( + column="dispatcher_id", + dimension=f"{namespace}.all_dispatchers", + dimension_column="dispatcher_id", + ) + source.link_dimension( + column="dispatcher_id", + dimension=f"{namespace}.all_dispatchers", + dimension_column="dispatcher_id", + ) + + # List dimensions for a metric + dj.metric(f"{namespace}.num_repair_orders").dimensions() + + # List common dimensions for multiple metrics + # names only + common_dimensions = dj.common_dimensions( + metrics=[ + "default.num_repair_orders", + "default.avg_repair_price", + "default.total_repair_cost", + ], + name_only=True, + ) + assert common_dimensions == [ + "default.dispatcher.company_name", + "default.dispatcher.dispatcher_id", + "default.dispatcher.phone", + "default.hard_hat.address", + "default.hard_hat.birth_date", + "default.hard_hat.city", + "default.hard_hat.contractor_id", + "default.hard_hat.country", + "default.hard_hat.first_name", + "default.hard_hat.hard_hat_id", + "default.hard_hat.hire_date", + "default.hard_hat.last_name", + "default.hard_hat.manager", + "default.hard_hat.postal_code", + "default.hard_hat.state", + "default.hard_hat.title", + "default.municipality_dim.contact_name", + "default.municipality_dim.contact_title", + "default.municipality_dim.local_region", + "default.municipality_dim.municipality_id", + "default.municipality_dim.municipality_type_desc", + "default.municipality_dim.municipality_type_id", + "default.municipality_dim.state_id", + "default.repair_orders.repair_order_id", + "default.us_state.state_abbr", + "default.us_state.state_id", + "default.us_state.state_name", + "default.us_state.state_region", + "default.us_state.state_region_description", + ] + # with details + common_dimensions = dj.common_dimensions( + metrics=[ + "default.num_repair_orders", + "default.avg_repair_price", + "default.total_repair_cost", + ], + ) + assert common_dimensions == [ + { + "name": "default.dispatcher.company_name", + "type": "string", + "path": [ + "default.repair_orders.repair_order_id", + "default.repair_order.dispatcher_id", + ], + }, + { + "name": "default.dispatcher.dispatcher_id", + "type": "int", + "path": [ + "default.repair_orders.repair_order_id", + "default.repair_order.dispatcher_id", + ], + }, + { + "name": "default.dispatcher.phone", + "type": "string", + "path": [ + "default.repair_orders.repair_order_id", + "default.repair_order.dispatcher_id", + ], + }, + { + "name": "default.hard_hat.address", + "type": "string", + "path": [ + "default.repair_orders.repair_order_id", + "default.repair_order.hard_hat_id", + ], + }, + { + "name": "default.hard_hat.birth_date", + "type": "date", + "path": [ + "default.repair_orders.repair_order_id", + "default.repair_order.hard_hat_id", + ], + }, + { + "name": "default.hard_hat.city", + "type": "string", + "path": [ + "default.repair_orders.repair_order_id", + "default.repair_order.hard_hat_id", + ], + }, + { + "name": "default.hard_hat.contractor_id", + "type": "int", + "path": [ + "default.repair_orders.repair_order_id", + "default.repair_order.hard_hat_id", + ], + }, + { + "name": "default.hard_hat.country", + "type": "string", + "path": [ + "default.repair_orders.repair_order_id", + "default.repair_order.hard_hat_id", + ], + }, + { + "name": "default.hard_hat.first_name", + "type": "string", + "path": [ + "default.repair_orders.repair_order_id", + "default.repair_order.hard_hat_id", + ], + }, + { + "name": "default.hard_hat.hard_hat_id", + "type": "int", + "path": [ + "default.repair_orders.repair_order_id", + "default.repair_order.hard_hat_id", + ], + }, + { + "name": "default.hard_hat.hire_date", + "type": "date", + "path": [ + "default.repair_orders.repair_order_id", + "default.repair_order.hard_hat_id", + ], + }, + { + "name": "default.hard_hat.last_name", + "type": "string", + "path": [ + "default.repair_orders.repair_order_id", + "default.repair_order.hard_hat_id", + ], + }, + { + "name": "default.hard_hat.manager", + "type": "int", + "path": [ + "default.repair_orders.repair_order_id", + "default.repair_order.hard_hat_id", + ], + }, + { + "name": "default.hard_hat.postal_code", + "type": "string", + "path": [ + "default.repair_orders.repair_order_id", + "default.repair_order.hard_hat_id", + ], + }, + { + "name": "default.hard_hat.state", + "type": "string", + "path": [ + "default.repair_orders.repair_order_id", + "default.repair_order.hard_hat_id", + ], + }, + { + "name": "default.hard_hat.title", + "type": "string", + "path": [ + "default.repair_orders.repair_order_id", + "default.repair_order.hard_hat_id", + ], + }, + { + "name": "default.municipality_dim.contact_name", + "type": "string", + "path": [ + "default.repair_orders.repair_order_id", + "default.repair_order.municipality_id", + ], + }, + { + "name": "default.municipality_dim.contact_title", + "type": "string", + "path": [ + "default.repair_orders.repair_order_id", + "default.repair_order.municipality_id", + ], + }, + { + "name": "default.municipality_dim.local_region", + "type": "string", + "path": [ + "default.repair_orders.repair_order_id", + "default.repair_order.municipality_id", + ], + }, + { + "name": "default.municipality_dim.municipality_id", + "type": "string", + "path": [ + "default.repair_orders.repair_order_id", + "default.repair_order.municipality_id", + ], + }, + { + "name": "default.municipality_dim.municipality_type_desc", + "type": "string", + "path": [ + "default.repair_orders.repair_order_id", + "default.repair_order.municipality_id", + ], + }, + { + "name": "default.municipality_dim.municipality_type_id", + "type": "string", + "path": [ + "default.repair_orders.repair_order_id", + "default.repair_order.municipality_id", + ], + }, + { + "name": "default.municipality_dim.state_id", + "type": "int", + "path": [ + "default.repair_orders.repair_order_id", + "default.repair_order.municipality_id", + ], + }, + {"name": "default.repair_orders.repair_order_id", "type": "int", "path": []}, + { + "name": "default.us_state.state_abbr", + "type": "string", + "path": [ + "default.repair_orders.repair_order_id", + "default.repair_order.hard_hat_id", + "default.hard_hat.state", + ], + }, + { + "name": "default.us_state.state_id", + "type": "int", + "path": [ + "default.repair_orders.repair_order_id", + "default.repair_order.hard_hat_id", + "default.hard_hat.state", + ], + }, + { + "name": "default.us_state.state_name", + "type": "string", + "path": [ + "default.repair_orders.repair_order_id", + "default.repair_order.hard_hat_id", + "default.hard_hat.state", + ], + }, + { + "name": "default.us_state.state_region", + "type": "int", + "path": [ + "default.repair_orders.repair_order_id", + "default.repair_order.hard_hat_id", + "default.hard_hat.state", + ], + }, + { + "name": "default.us_state.state_region_description", + "type": "string", + "path": [ + "default.repair_orders.repair_order_id", + "default.repair_order.hard_hat_id", + "default.hard_hat.state", + ], + }, + ] + + # List common metrics for multiple dimensions + # names only + common_metrics = dj.common_metrics( + dimensions=["default.date_dim", "default.repair_order"], + name_only=True, + ) + assert common_metrics == [ + "default.num_repair_orders", + "default.avg_repair_price", + "default.total_repair_cost", + "default.total_repair_order_discounts", + "default.avg_repair_order_discounts", + "default.avg_time_to_dispatch", + ] + # with details + common_metrics = dj.common_metrics( + dimensions=["default.date_dim", "default.repair_order"], + ) + assert common_metrics == [ + { + "name": "default.num_repair_orders", + "display_name": "Default: Num Repair Orders", + "description": "Number of repair orders", + "query": "SELECT count(repair_order_id) default_DOT_num_repair_orders \n FROM default.repair_orders\n\n", + }, + { + "name": "default.avg_repair_price", + "display_name": "Default: Avg Repair Price", + "description": "Average repair price", + "query": "SELECT avg(price) default_DOT_avg_repair_price \n FROM default.repair_order_details\n\n", + }, + { + "name": "default.total_repair_cost", + "display_name": "Default: Total Repair Cost", + "description": "Total repair cost", + "query": "SELECT sum(price) default_DOT_total_repair_cost \n FROM default.repair_order_details\n\n", + }, + { + "name": "default.total_repair_order_discounts", + "display_name": "Default: Total Repair Order Discounts", + "description": "Total repair order discounts", + "query": "SELECT sum(price * discount) default_DOT_total_repair_order_discounts \n FROM default.repair_order_details\n\n", + }, + { + "name": "default.avg_repair_order_discounts", + "display_name": "Default: Avg Repair Order Discounts", + "description": "Total repair order discounts", + "query": "SELECT avg(price * discount) default_DOT_avg_repair_order_discounts \n FROM default.repair_order_details\n\n", + }, + { + "name": "default.avg_time_to_dispatch", + "display_name": "Default: Avg Time To Dispatch", + "description": "Average time to dispatch a repair order", + "query": "SELECT avg(dispatched_date - order_date) default_DOT_avg_time_to_dispatch \n FROM default.repair_orders\n\n", + }, + ] + + # Get SQL for a set of metrics and dimensions + query = dj.sql( + metrics=[ + "default.num_repair_orders", + "default.avg_repair_price", + "default.total_repair_cost", + ], + dimensions=[ + "default.us_state.state_abbr", + "default.us_state.state_id", + "default.us_state.state_name", + ], + filters=[], + ) + expected_query = """ + WITH + m0_default_DOT_num_repair_orders AS (SELECT default_DOT_us_state.state_abbr, + default_DOT_us_state.state_id, + default_DOT_us_state.state_name, + count(default_DOT_repair_orders.repair_order_id) default_DOT_num_repair_orders + FROM roads.repair_orders AS default_DOT_repair_orders LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders) + AS default_DOT_repair_order ON default_DOT_repair_orders.repair_order_id = default_DOT_repair_order.repair_order_id + LEFT OUTER JOIN (SELECT default_DOT_hard_hats.birth_date, + default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.hire_date, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats) + AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + LEFT OUTER JOIN (SELECT default_DOT_us_states.state_abbr, + default_DOT_us_states.state_id, + default_DOT_us_states.state_name + FROM roads.us_states AS default_DOT_us_states LEFT JOIN roads.us_region AS default_DOT_us_region ON default_DOT_us_states.state_region = default_DOT_us_region.us_region_description) + AS default_DOT_us_state ON default_DOT_hard_hat.state = default_DOT_us_state.state_abbr + GROUP BY default_DOT_us_state.state_abbr, default_DOT_us_state.state_id, default_DOT_us_state.state_name + ), + m1_default_DOT_avg_repair_price AS (SELECT default_DOT_us_state.state_abbr, + default_DOT_us_state.state_id, + default_DOT_us_state.state_name, + avg(default_DOT_repair_order_details.price) default_DOT_avg_repair_price + FROM roads.repair_order_details AS default_DOT_repair_order_details LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders) + AS default_DOT_repair_order ON default_DOT_repair_order_details.repair_order_id = default_DOT_repair_order.repair_order_id + LEFT OUTER JOIN (SELECT default_DOT_hard_hats.birth_date, + default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.hire_date, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats) + AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + LEFT OUTER JOIN (SELECT default_DOT_us_states.state_abbr, + default_DOT_us_states.state_id, + default_DOT_us_states.state_name + FROM roads.us_states AS default_DOT_us_states LEFT JOIN roads.us_region AS default_DOT_us_region ON default_DOT_us_states.state_region = default_DOT_us_region.us_region_description) + AS default_DOT_us_state ON default_DOT_hard_hat.state = default_DOT_us_state.state_abbr + GROUP BY default_DOT_us_state.state_abbr, default_DOT_us_state.state_id, default_DOT_us_state.state_name + ), + m2_default_DOT_total_repair_cost AS (SELECT default_DOT_us_state.state_abbr, + default_DOT_us_state.state_id, + default_DOT_us_state.state_name, + sum(default_DOT_repair_order_details.price) default_DOT_total_repair_cost + FROM roads.repair_order_details AS default_DOT_repair_order_details LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders) + AS default_DOT_repair_order ON default_DOT_repair_order_details.repair_order_id = default_DOT_repair_order.repair_order_id + LEFT OUTER JOIN (SELECT default_DOT_hard_hats.birth_date, + default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.hire_date, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats) + AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + LEFT OUTER JOIN (SELECT default_DOT_us_states.state_abbr, + default_DOT_us_states.state_id, + default_DOT_us_states.state_name + FROM roads.us_states AS default_DOT_us_states LEFT JOIN roads.us_region AS default_DOT_us_region ON default_DOT_us_states.state_region = default_DOT_us_region.us_region_description) + AS default_DOT_us_state ON default_DOT_hard_hat.state = default_DOT_us_state.state_abbr + GROUP BY default_DOT_us_state.state_abbr, default_DOT_us_state.state_id, default_DOT_us_state.state_name + )SELECT m0_default_DOT_num_repair_orders.default_DOT_num_repair_orders, + m1_default_DOT_avg_repair_price.default_DOT_avg_repair_price, + m2_default_DOT_total_repair_cost.default_DOT_total_repair_cost, + COALESCE(m0_default_DOT_num_repair_orders.state_abbr, m1_default_DOT_avg_repair_price.state_abbr, m2_default_DOT_total_repair_cost.state_abbr) state_abbr, + COALESCE(m0_default_DOT_num_repair_orders.state_id, m1_default_DOT_avg_repair_price.state_id, m2_default_DOT_total_repair_cost.state_id) state_id, + COALESCE(m0_default_DOT_num_repair_orders.state_name, m1_default_DOT_avg_repair_price.state_name, m2_default_DOT_total_repair_cost.state_name) state_name + FROM m0_default_DOT_num_repair_orders FULL OUTER JOIN m1_default_DOT_avg_repair_price ON m0_default_DOT_num_repair_orders.state_abbr = m1_default_DOT_avg_repair_price.state_abbr AND m0_default_DOT_num_repair_orders.state_id = m1_default_DOT_avg_repair_price.state_id AND m0_default_DOT_num_repair_orders.state_name = m1_default_DOT_avg_repair_price.state_name + FULL OUTER JOIN m2_default_DOT_total_repair_cost ON m0_default_DOT_num_repair_orders.state_abbr = m2_default_DOT_total_repair_cost.state_abbr AND m0_default_DOT_num_repair_orders.state_id = m2_default_DOT_total_repair_cost.state_id AND m0_default_DOT_num_repair_orders.state_name = m2_default_DOT_total_repair_cost.state_name + """ + + assert "".join(query.split()) == "".join(expected_query.split()) + + # Get data for a set of metrics and dimensions + result = dj.data( + metrics=[ + "default.num_repair_orders", + "default.avg_repair_price", + "default.total_repair_cost", + ], + dimensions=[ + "default.us_state.state_abbr", + "default.us_state.state_id", + "default.us_state.state_name", + ], + async_=False, + ) + expected_df = pandas.DataFrame.from_dict( + { + "default_DOT_num_repair_orders": { + 0: 2, + 1: 2, + 2: 3, + 3: 3, + 4: 5, + 5: 4, + 6: 1, + 7: 1, + 8: 4, + }, + "default_DOT_avg_repair_price": { + 0: 65682.0, + 1: 39301.5, + 2: 65595.66666666667, + 3: 76555.33333333333, + 4: 64190.6, + 5: 54672.75, + 6: 53374.0, + 7: 70418.0, + 8: 54083.5, + }, + "default_DOT_total_repair_cost": { + 0: 131364.0, + 1: 78603.0, + 2: 196787.0, + 3: 229666.0, + 4: 320953.0, + 5: 218691.0, + 6: 53374.0, + 7: 70418.0, + 8: 216334.0, + }, + "state_abbr": { + 0: "AZ", + 1: "CT", + 2: "GA", + 3: "MA", + 4: "MI", + 5: "NJ", + 6: "NY", + 7: "OK", + 8: "PA", + }, + "state_id": {0: 3, 1: 7, 2: 11, 3: 22, 4: 23, 5: 31, 6: 33, 7: 37, 8: 39}, + "state_name": { + 0: "Arizona", + 1: "Connecticut", + 2: "Georgia", + 3: "Massachusetts", + 4: "Michigan", + 5: "New Jersey", + 6: "New York", + 7: "Oklahoma", + 8: "Pennsylvania", + }, + }, + ) + pandas.testing.assert_frame_equal(result, expected_df) + + # Create a transform 2 downstream from a transform 1 + dj.create_transform( + name=f"{namespace}.repair_orders_w_hard_hats", + description="Repair orders that have a hard hat assigned", + query=f""" + SELECT + repair_order_id, + municipality_id, + hard_hat_id, + dispatcher_id + FROM {namespace}.repair_orders_w_dispatchers + WHERE hard_hat_id IS NOT NULL + """, + ) + + # Get transform 2 that's downstream from transform 1 and make sure it's valid + transform_2 = dj.transform(f"{namespace}.repair_orders_w_hard_hats") + assert transform_2.status == NodeStatus.VALID + + # Add an availability state to the transform + response = transform_2.add_availability( + AvailabilityState( + catalog="default", + schema_="materialized", + table="contractor", + valid_through_ts=1688660209, + ), + ) + assert response == {"message": "Availability state successfully posted"} + + # Add an availability state to the transform + response = transform_2.set_column_attributes( + [ + ColumnAttribute( + attribute_type_name="dimension", + column_name="hard_hat_id", + ), + ], + ) + assert response == [ + { + "attributes": [ + {"attribute_type": {"name": "dimension", "namespace": "system"}}, + ], + "dimension": None, + "name": "hard_hat_id", + "type": "int", + }, + ] + + # Create a draft transform 4 that's downstream from a not yet created transform 3 + dj.create_transform( + name=f"{namespace}.repair_orders_w_repair_order_id", + description="Repair orders without a null ID", + query=f""" + SELECT + repair_order_id, + municipality_id, + hard_hat_id, + dispatcher_id + FROM {namespace}.repair_orders_w_municipalities + WHERE repair_order_id IS NOT NULL + """, + mode=NodeMode.DRAFT, + ) + transform_4 = dj.transform(f"{namespace}.repair_orders_w_repair_order_id") + transform_4.refresh() + assert transform_4.mode == NodeMode.DRAFT + assert transform_4.status == NodeStatus.INVALID + # Check that transform 4 is invalid because transform 3 does not exist + with pytest.raises(DJClientException): + transform_4._validate() + + # Create a draft transform 3 that's downstream from transform 2 + dj.create_transform( + name=f"{namespace}.repair_orders_w_municipalities", + description="Repair orders that have a municipality listed", + query=f""" + SELECT + repair_order_id, + municipality_id, + hard_hat_id, + dispatcher_id + FROM {namespace}.repair_orders_w_hard_hats + WHERE municipality_id IS NOT NULL + """, + mode=NodeMode.DRAFT, + ) + transform_3 = dj.transform(f"{namespace}.repair_orders_w_municipalities") + # Check that transform 3 is valid + assert transform_3._validate() == NodeStatus.VALID + + # Check that transform 4 is now valid after transform 3 was created + transform_4.refresh() + assert transform_4._validate() == NodeStatus.VALID + + # Check that publishing transform 3 works + transform_3.publish() diff --git a/datajunction-clients/python/tests/test_models.py b/datajunction-clients/python/tests/test_models.py new file mode 100644 index 000000000..f77c5663e --- /dev/null +++ b/datajunction-clients/python/tests/test_models.py @@ -0,0 +1,17 @@ +"""Tests for models.""" +from datajunction.models import QueryState + + +def test_enum_list(): + """ + Check list of query states works + """ + assert QueryState.list() == [ + "UNKNOWN", + "ACCEPTED", + "SCHEDULED", + "RUNNING", + "FINISHED", + "CANCELED", + "FAILED", + ] diff --git a/datajunction-clients/python/tox.ini b/datajunction-clients/python/tox.ini new file mode 100644 index 000000000..f72ab293e --- /dev/null +++ b/datajunction-clients/python/tox.ini @@ -0,0 +1,91 @@ +# Tox configuration file + +[tox] +minversion = 3.24 +envlist = default +isolated_build = True + + +[testenv] +description = Invoke pytest to run automated tests +setenv = + TOXINIDIR = {toxinidir} +passenv = + HOME + SETUPTOOLS_* +extras = + testing +commands = + pytest {posargs} + + +# # To run `tox -e lint` you need to make sure you have a +# # `.pre-commit-config.yaml` file. See https://pre-commit.com +# [testenv:lint] +# description = Perform static analysis and style checks +# skip_install = True +# deps = pre-commit +# passenv = +# HOMEPATH +# PROGRAMDATA +# SETUPTOOLS_* +# commands = +# pre-commit run --all-files {posargs:--show-diff-on-failure} + + +[testenv:{build,clean}] +description = + build: Build the package in isolation according to PEP517, see https://github.com/pypa/build + clean: Remove old distribution files and temporary build artifacts (./build and ./dist) +# https://setuptools.pypa.io/en/stable/build_meta.html#how-to-use-it +skip_install = True +changedir = {toxinidir} +deps = + build: build[virtualenv] +passenv = + SETUPTOOLS_* +commands = + clean: python -c 'import shutil; [shutil.rmtree(p, True) for p in ("build", "dist", "docs/_build")]' + clean: python -c 'import pathlib, shutil; [shutil.rmtree(p, True) for p in pathlib.Path("src").glob("*.egg-info")]' + build: python -m build {posargs} +# By default, both `sdist` and `wheel` are built. If your sdist is too big or you don't want +# to make it available, consider running: `tox -e build -- --wheel` + + +[testenv:{docs,doctests,linkcheck}] +description = + docs: Invoke sphinx-build to build the docs + doctests: Invoke sphinx-build to run doctests + linkcheck: Check for broken links in the documentation +passenv = + SETUPTOOLS_* +setenv = + DOCSDIR = {toxinidir}/docs + BUILDDIR = {toxinidir}/docs/_build + docs: BUILD = html + doctests: BUILD = doctest + linkcheck: BUILD = linkcheck +deps = + -r {toxinidir}/docs/requirements.txt + # ^ requirements.txt shared with Read The Docs +commands = + sphinx-build --color -b {env:BUILD} -d "{env:BUILDDIR}/doctrees" "{env:DOCSDIR}" "{env:BUILDDIR}/{env:BUILD}" {posargs} + + +[testenv:publish] +description = + Publish the package you have been developing to a package index server. + By default, it uses testpypi. If you really want to publish your package + to be publicly accessible in PyPI, use the `-- --repository pypi` option. +skip_install = True +changedir = {toxinidir} +passenv = + # See: https://twine.readthedocs.io/en/latest/ + TWINE_USERNAME + TWINE_PASSWORD + TWINE_REPOSITORY + TWINE_REPOSITORY_URL +deps = twine +commands = + python -m twine check dist/* + python -m twine upload {posargs:--repository {env:TWINE_REPOSITORY:testpypi}} dist/* diff --git a/datajunction-query/.coveragerc b/datajunction-query/.coveragerc new file mode 100644 index 000000000..6ef1179a8 --- /dev/null +++ b/datajunction-query/.coveragerc @@ -0,0 +1,31 @@ +# .coveragerc to control coverage.py +[run] +branch = True +source = dj +# omit = bad_file.py + +[paths] +source = + djqs/ + */site-packages/ + +[report] +sort = -Cover +# Regexes for lines to exclude from consideration +exclude_lines = + # Have to re-enable the standard pragma + pragma: no cover + + # Don't complain about missing debug-only code: + def __repr__ + if self\.debug + + # Don't complain if tests don't hit defensive assertion code: + raise AssertionError + raise NotImplementedError + + # Don't complain if non-runnable code isn't run: + if 0: + if __name__ == .__main__.: + + if TYPE_CHECKING: diff --git a/datajunction-query/.flake8 b/datajunction-query/.flake8 new file mode 100644 index 000000000..d9ad0b409 --- /dev/null +++ b/datajunction-query/.flake8 @@ -0,0 +1,5 @@ +[flake8] +ignore = E203, E266, E501, W503, F403, F401 +max-line-length = 79 +max-complexity = 18 +select = B,C,E,F,W,T4,B9 diff --git a/datajunction-query/.gitignore b/datajunction-query/.gitignore new file mode 100644 index 000000000..e48e6169a --- /dev/null +++ b/datajunction-query/.gitignore @@ -0,0 +1,117 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/api/* +docs/_rst/* +docs/_build/* + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# virtualenv +.venv +venv/ +ENV/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +*.sqlite +*.db +*.swp + +# https://pypi.org/project/python-dotenv/ +.env + +# VS Code +.vscode + +# IDEA +.idea + +# Spark +spark-warehouse +metastore_db diff --git a/datajunction-query/.isort.cfg b/datajunction-query/.isort.cfg new file mode 100644 index 000000000..93c4c7eae --- /dev/null +++ b/datajunction-query/.isort.cfg @@ -0,0 +1,3 @@ +[settings] +profile = black +known_first_party = djqs diff --git a/datajunction-query/.pre-commit-config.yaml b/datajunction-query/.pre-commit-config.yaml new file mode 100644 index 000000000..783c901e7 --- /dev/null +++ b/datajunction-query/.pre-commit-config.yaml @@ -0,0 +1,97 @@ +files: ^datajunction-query/ +exclude: (^datajunction-query/docker/) + +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.1.0 + hooks: + - id: trailing-whitespace + - id: check-added-large-files + - id: check-ast + exclude: ^templates/ + - id: check-json + - id: check-merge-conflict + - id: check-xml + - id: check-yaml + - id: debug-statements + exclude: ^templates/ + - id: end-of-file-fixer + exclude: openapi.json + - id: requirements-txt-fixer + exclude: ^templates/ + - id: mixed-line-ending + args: ['--fix=auto'] # replace 'auto' with 'lf' to enforce Linux/Mac line endings or 'crlf' for Windows + +## If you want to avoid flake8 errors due to unused vars or imports: +# - repo: https://github.com/myint/autoflake.git +# rev: v1.4 +# hooks: +# - id: autoflake +# args: [ +# --in-place, +# --remove-all-unused-imports, +# --remove-unused-variables, +# ] + +- repo: https://github.com/pycqa/isort + rev: 5.12.0 + hooks: + - id: isort + +- repo: https://github.com/psf/black + rev: 22.8.0 + hooks: + - id: black + language_version: python3 + exclude: ^templates/ + +## If like to embrace black styles even in the docs: +# - repo: https://github.com/asottile/blacken-docs +# rev: v1.9.1 +# hooks: +# - id: blacken-docs +# additional_dependencies: [black] + +- repo: https://github.com/PyCQA/flake8 + rev: 3.9.2 + hooks: + - id: flake8 + exclude: ^templates/ + ## You can add flake8 plugins via `additional_dependencies`: + # additional_dependencies: [flake8-bugbear] + +- repo: https://github.com/pre-commit/mirrors-mypy + rev: 'v0.931' # Use the sha / tag you want to point at + hooks: + - id: mypy + exclude: ^templates/ + additional_dependencies: + - types-requests + - types-freezegun + - types-python-dateutil + - types-pkg_resources + - types-PyYAML + - types-tabulate +- repo: https://github.com/asottile/add-trailing-comma + rev: v2.2.1 + hooks: + - id: add-trailing-comma +#- repo: https://github.com/asottile/reorder_python_imports +# rev: v2.5.0 +# hooks: +# - id: reorder-python-imports +# args: [--application-directories=.:src] +- repo: https://github.com/hadialqattan/pycln + rev: v2.1.7 # Possible releases: https://github.com/hadialqattan/pycln/tags + hooks: + - id: pycln + args: [--config=pyproject.toml] + exclude: ^templates/ +- repo: local + hooks: + - id: pylint + name: pylint + entry: pylint --disable=duplicate-code,use-implicit-booleaness-not-comparison + language: system + types: [python] + exclude: ^templates/ diff --git a/datajunction-query/.pylintrc b/datajunction-query/.pylintrc new file mode 100644 index 000000000..41e6bd54f --- /dev/null +++ b/datajunction-query/.pylintrc @@ -0,0 +1,9 @@ +[MESSAGES CONTROL] + +[MASTER] +# https://github.com/samuelcolvin/pydantic/issues/1961#issuecomment-759522422 +extension-pkg-whitelist=pydantic +ignore=templates,docs + +[IMPORTS] +ignore-modules=duckdb diff --git a/.readthedocs.yml b/datajunction-query/.readthedocs.yml similarity index 100% rename from .readthedocs.yml rename to datajunction-query/.readthedocs.yml diff --git a/datajunction-query/AUTHORS.rst b/datajunction-query/AUTHORS.rst new file mode 100644 index 000000000..30d306349 --- /dev/null +++ b/datajunction-query/AUTHORS.rst @@ -0,0 +1,9 @@ +============ +Contributors +============ + +* Beto Dealmeida +* Olek Gorajek +* Hamidreza Hashemi +* Ali Raza +* Sam Redai diff --git a/datajunction-query/CODE_OF_CONDUCT.md b/datajunction-query/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..18c914718 --- /dev/null +++ b/datajunction-query/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/datajunction-query/Dockerfile b/datajunction-query/Dockerfile new file mode 100644 index 000000000..44d937ec3 --- /dev/null +++ b/datajunction-query/Dockerfile @@ -0,0 +1,7 @@ +FROM jupyter/pyspark-notebook +USER root +WORKDIR /code +COPY . /code +RUN pip install -e .[uvicorn] +CMD ["uvicorn", "djqs.api.main:app", "--host", "0.0.0.0", "--port", "8001", "--reload"] +EXPOSE 8001 diff --git a/datajunction-query/LICENSE.txt b/datajunction-query/LICENSE.txt new file mode 100644 index 000000000..c4b4b0a2e --- /dev/null +++ b/datajunction-query/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2021 Beto Dealmeida + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/datajunction-query/Makefile b/datajunction-query/Makefile new file mode 100644 index 000000000..5d200f7bf --- /dev/null +++ b/datajunction-query/Makefile @@ -0,0 +1,42 @@ +pyenv: .python-version + +.python-version: setup.cfg + if [ -z "`pyenv virtualenvs | grep djqs`" ]; then\ + pyenv virtualenv djqs;\ + fi + if [ ! -f .python-version ]; then\ + pyenv local djqs;\ + fi + pdm install + touch .python-version + +docker-build: + docker build . + docker compose build + +docker-run: + docker compose up + +docker-run-with-postgres: + docker compose -f docker-compose.yml -f docker-compose.postgres.yml up + +docker-run-with-druid: + docker compose -f docker-compose.yml -f docker-compose.druid.yml up + +docker-run-with-cockroachdb: + docker compose -f docker-compose.yml -f docker-compose.cockroachdb.yml up + +test: pyenv + pdm run pytest --cov=djqs -vv tests/ --doctest-modules djqs --without-integration --without-slow-integration ${PYTEST_ARGS} + +integration: pyenv + pdm run pytest --cov=djqs -vv tests/ --doctest-modules djqs --with-integration --with-slow-integration + +clean: + pyenv virtualenv-delete djqs + +spellcheck: + codespell -L froms -S "*.json" djqs docs/*rst tests templates + +check: + pdm run pre-commit run --all-files diff --git a/datajunction-query/README.rst b/datajunction-query/README.rst new file mode 100644 index 000000000..65b0dbf2a --- /dev/null +++ b/datajunction-query/README.rst @@ -0,0 +1,334 @@ +========================== +DataJunction Query Service +========================== + +This repository (DJQS) is an open source implementation of a `DataJunction `_ +query service. It allows you to create catalogs and engines that represent sqlalchemy connections. Configuring +a DJ server to use a DJQS server allows DJ to query any of the database technologies supported by sqlalchemy. + +========== +Quickstart +========== + +To get started, clone this repo and start up the docker compose environment. + +.. code-block:: + + git clone https://github.com/DataJunction/djqs + cd djqs + docker compose up + +Creating Catalogs +================= + +Catalogs can be created using the :code:`POST /catalogs/` endpoint. + +.. code-block:: sh + + curl -X 'POST' \ + 'http://localhost:8001/catalogs/' \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -d '{ + "name": "djdb" + }' + +Creating Engines +================ + +Engines can be created using the :code:`POST /engines/` endpoint. + +.. code-block:: sh + + curl -X 'POST' \ + 'http://localhost:8001/engines/' \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -d '{ + "name": "sqlalchemy-postgresql", + "version": "15.2", + "uri": "postgresql://dj:dj@postgres-roads:5432/djdb" + }' + +Engines can be attached to existing catalogs using the :code:`POST /catalogs/{name}/engines/` endpoint. + +.. code-block:: sh + + curl -X 'POST' \ + 'http://localhost:8001/catalogs/djdb/engines/' \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -d '[ + { + "name": "sqlalchemy-postgresql", + "version": "15.2" + } + ]' + +Executing Queries +================= + +Queries can be submitted to DJQS for a specified catalog and engine. + +.. code-block:: sh + + curl -X 'POST' \ + 'http://localhost:8001/queries/' \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -d '{ + "catalog_name": "djdb", + "engine_name": "sqlalchemy-postgresql", + "engine_version": "15.2", + "submitted_query": "SELECT * from roads.repair_orders", + "async_": false + }' + +Async queries can be submitted as well. + +.. code-block:: sh + + curl -X 'POST' \ + 'http://localhost:8001/queries/' \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -d '{ + "catalog_name": "djdb", + "engine_name": "sqlalchemy-postgresql", + "engine_version": "15.2", + "submitted_query": "SELECT * from roads.repair_orders", + "async_": true + }' + +*response* + +.. code-block:: json + + { + "catalog_name": "djdb", + "engine_name": "sqlalchemy-postgresql", + "engine_version": "15.2", + "id": "", + "submitted_query": "SELECT * from roads.repair_orders", + "executed_query": null, + "scheduled": null, + "started": null, + "finished": null, + "state": "ACCEPTED", + "progress": 0, + "results": [], + "next": null, + "previous": null, + "errors": [] + } + +The query id provided in the response can then be used to check the status of the running query and get the results +once it's completed. + +.. code-block:: sh + + curl -X 'GET' \ + 'http://localhost:8001/queries//' \ + -H 'accept: application/json' + +*response* + +.. code-block:: json + + { + "catalog_name": "djdb", + "engine_name": "sqlalchemy-postgresql", + "engine_version": "15.2", + "id": "$QUERY_ID", + "submitted_query": "SELECT * from roads.repair_orders", + "executed_query": "SELECT * from roads.repair_orders", + "scheduled": "2023-02-28T07:27:55.367162", + "started": "2023-02-28T07:27:55.367387", + "finished": "2023-02-28T07:27:55.502412", + "state": "FINISHED", + "progress": 1, + "results": [ + { + "sql": "SELECT * from roads.repair_orders", + "columns": [...], + "rows": [...], + "row_count": 25 + } + ], + "next": null, + "previous": null, + "errors": [] + } + +Reflection +========== + +If running a [reflection service](https://github.com/DataJunction/djrs), that service can leverage the +:code:`POST /table/{table}/columns/` endpoint of DJQS to get column names and types for a given table. + +.. code-block:: sh + + curl -X 'GET' \ + 'http://localhost:8001/table/djdb.roads.repair_orders/columns/?engine=sqlalchemy-postgresql&engine_version=15.2' \ + -H 'accept: application/json' + +*response* + +.. code-block:: json + + { + "name": "djdb.roads.repair_orders", + "columns": [ + { + "name": "repair_order_id", + "type": "INT" + }, + { + "name": "municipality_id", + "type": "STR" + }, + { + "name": "hard_hat_id", + "type": "INT" + }, + { + "name": "order_date", + "type": "DATE" + }, + { + "name": "required_date", + "type": "DATE" + }, + { + "name": "dispatched_date", + "type": "DATE" + }, + { + "name": "dispatcher_id", + "type": "INT" + } + ] + } + +====== +DuckDB +====== + +DJQS includes an example of using DuckDB as an engine and it comes preloaded with the roads example database. + +Create a :code:`djduckdb` catalog and a :code:`duckdb` engine. + +.. code-block:: + + curl -X 'POST' \ + 'http://localhost:8001/catalogs/' \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -d '{ + "name": "djduckdb" + }' + +.. code-block:: + + curl -X 'POST' \ + 'http://localhost:8001/engines/' \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -d '{ + "name": "duckdb", + "version": "0.7.1", + "uri": "duckdb://local[*]" + }' + +.. code-block:: + + curl -X 'POST' \ + 'http://localhost:8001/catalogs/djduckdb/engines/' \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -d '[ + { + "name": "duckdb", + "version": "0.7.1" + } + ]' + +Now you can submit DuckDB SQL queries. + +.. code-block:: + + curl -X 'POST' \ + 'http://localhost:8001/queries/' \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -d '{ + "catalog_name": "djduckdb", + "engine_name": "duckdb", + "engine_version": "0.7.1", + "submitted_query": "SELECT * FROM roads.us_states LIMIT 10", + "async_": false + }' + +===== +Spark +===== + +DJQS includes an example of using Spark as an engine. To try it, start up the docker compose environment and then +load the example roads database into Spark. + +.. code-block:: + + docker exec -it djqs /bin/bash -c "python /code/docker/spark_load_roads.py" + +Next, create a :code:`djspark` catalog and a :code:`spark` engine. + +.. code-block:: + + curl -X 'POST' \ + 'http://localhost:8001/catalogs/' \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -d '{ + "name": "djspark" + }' + +.. code-block:: + + curl -X 'POST' \ + 'http://localhost:8001/engines/' \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -d '{ + "name": "spark", + "version": "3.3.2", + "uri": "spark://local[*]" + }' + +.. code-block:: + + curl -X 'POST' \ + 'http://localhost:8001/catalogs/djspark/engines/' \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -d '[ + { + "name": "spark", + "version": "3.3.2" + } + ]' + +Now you can submit Spark SQL queries. + +.. code-block:: + + curl -X 'POST' \ + 'http://localhost:8001/queries/' \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -d '{ + "catalog_name": "djspark", + "engine_name": "spark", + "engine_version": "3.3.2", + "submitted_query": "SELECT * FROM roads.us_states LIMIT 10", + "async_": false + }' diff --git a/datajunction-query/alembic.ini b/datajunction-query/alembic.ini new file mode 100644 index 000000000..5716e4937 --- /dev/null +++ b/datajunction-query/alembic.ini @@ -0,0 +1,104 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts +script_location = alembic + +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s +file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s + +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. +prepend_sys_path = . + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the python-dateutil library that can be +# installed by adding `alembic[tz]` to the pip requirements +# string value is passed to dateutil.tz.gettz() +# leave blank for localtime +# timezone = +timezone = UTC + +# max length of characters to apply to the +# "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; This defaults +# to alembic/versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "version_path_separator" below. +# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions + +# version path separator; As mentioned above, this is the character used to split +# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. +# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. +# Valid values for version_path_separator are: +# +# version_path_separator = : +# version_path_separator = ; +# version_path_separator = space +version_path_separator = os # Use os.pathsep. Default configuration used for new projects. + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +sqlalchemy.url = driver://user:pass@localhost/dbname + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/datajunction-query/alembic/README b/datajunction-query/alembic/README new file mode 100644 index 000000000..2500aa1bc --- /dev/null +++ b/datajunction-query/alembic/README @@ -0,0 +1 @@ +Generic single-database configuration. diff --git a/datajunction-query/alembic/env.py b/datajunction-query/alembic/env.py new file mode 100644 index 000000000..ad293892e --- /dev/null +++ b/datajunction-query/alembic/env.py @@ -0,0 +1,82 @@ +""" +Environment for Alembic migrations. +""" +# pylint: disable=no-member, unused-import, no-name-in-module, import-error + +from logging.config import fileConfig + +from sqlmodel import SQLModel, create_engine + +from alembic import context +from djqs.models.catalog import Catalog +from djqs.models.engine import Engine +from djqs.models.query import Query +from djqs.utils import get_settings + +settings = get_settings() + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +if config.config_file_name is not None: + fileConfig(config.config_file_name) + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +target_metadata = SQLModel.metadata + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = settings.index + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + connectable = create_engine(settings.index) + + with connectable.connect() as connection: + context.configure(connection=connection, target_metadata=target_metadata) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/datajunction-query/alembic/script.py.mako b/datajunction-query/alembic/script.py.mako new file mode 100644 index 000000000..1c2bf5315 --- /dev/null +++ b/datajunction-query/alembic/script.py.mako @@ -0,0 +1,27 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +# pylint: disable=no-member, invalid-name, missing-function-docstring, unused-import, no-name-in-module + +import sqlalchemy as sa +import sqlmodel +from alembic import op +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/datajunction-query/alembic/versions/2023_02_28_0541-a7e11a2438b4_initial_migration.py b/datajunction-query/alembic/versions/2023_02_28_0541-a7e11a2438b4_initial_migration.py new file mode 100644 index 000000000..cb3e6e4d1 --- /dev/null +++ b/datajunction-query/alembic/versions/2023_02_28_0541-a7e11a2438b4_initial_migration.py @@ -0,0 +1,86 @@ +"""Initial migration + +Revision ID: a7e11a2438b4 +Revises: +Create Date: 2023-02-28 05:41:11.232497+00:00 + +""" +# pylint: disable=no-member, invalid-name, missing-function-docstring, unused-import, no-name-in-module + +import sqlalchemy as sa +import sqlalchemy_utils +import sqlmodel + +from alembic import op + +# revision identifiers, used by Alembic. +revision = "a7e11a2438b4" +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "catalog", + sa.Column("uuid", sqlalchemy_utils.types.uuid.UUIDType(), nullable=True), + sa.Column("created_at", sa.DateTime(timezone=True), nullable=True), + sa.Column("updated_at", sa.DateTime(timezone=True), nullable=True), + sa.Column("extra_params", sa.JSON(), nullable=True), + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.PrimaryKeyConstraint("id"), + ) + op.create_table( + "engine", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("version", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("uri", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.PrimaryKeyConstraint("id"), + ) + op.create_table( + "query", + sa.Column("id", sqlalchemy_utils.types.uuid.UUIDType(), nullable=False), + sa.Column("catalog_name", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("engine_name", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("engine_version", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column( + "submitted_query", + sqlmodel.sql.sqltypes.AutoString(), + nullable=False, + ), + sa.Column("async_", sa.Boolean(), nullable=False), + sa.Column("executed_query", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column("scheduled", sa.DateTime(), nullable=True), + sa.Column("started", sa.DateTime(), nullable=True), + sa.Column("finished", sa.DateTime(), nullable=True), + sa.Column("state", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("progress", sa.Float(), nullable=False), + sa.PrimaryKeyConstraint("id"), + ) + op.create_table( + "catalogengines", + sa.Column("catalog_id", sa.Integer(), nullable=False), + sa.Column("engine_id", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint( + ["catalog_id"], + ["catalog.id"], + ), + sa.ForeignKeyConstraint( + ["engine_id"], + ["engine.id"], + ), + sa.PrimaryKeyConstraint("catalog_id", "engine_id"), + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table("catalogengines") + op.drop_table("query") + op.drop_table("engine") + op.drop_table("catalog") + # ### end Alembic commands ### diff --git a/datajunction-query/djqs.demo.db b/datajunction-query/djqs.demo.db new file mode 100644 index 000000000..0963897dc Binary files /dev/null and b/datajunction-query/djqs.demo.db differ diff --git a/datajunction-query/djqs/__about__.py b/datajunction-query/djqs/__about__.py new file mode 100644 index 000000000..3fdaaa2ff --- /dev/null +++ b/datajunction-query/djqs/__about__.py @@ -0,0 +1,4 @@ +""" +Version for Hatch +""" +__version__ = "0.0.1a1" diff --git a/datajunction-query/djqs/__init__.py b/datajunction-query/djqs/__init__.py new file mode 100644 index 000000000..2cff295fb --- /dev/null +++ b/datajunction-query/djqs/__init__.py @@ -0,0 +1,14 @@ +""" +Package version and name. +""" + +from importlib.metadata import PackageNotFoundError, version # pragma: no cover + +try: + # Change here if project is renamed and does not equal the package name + DIST_NAME = __name__ + __version__ = version(DIST_NAME) +except PackageNotFoundError: # pragma: no cover + __version__ = "unknown" +finally: + del version, PackageNotFoundError diff --git a/tests/cli/__init__.py b/datajunction-query/djqs/api/__init__.py similarity index 100% rename from tests/cli/__init__.py rename to datajunction-query/djqs/api/__init__.py diff --git a/datajunction-query/djqs/api/catalogs.py b/datajunction-query/djqs/api/catalogs.py new file mode 100644 index 000000000..c8d2db63d --- /dev/null +++ b/datajunction-query/djqs/api/catalogs.py @@ -0,0 +1,110 @@ +""" +Catalog related APIs. +""" + +import logging +from http import HTTPStatus +from typing import List + +from fastapi import APIRouter, Depends, HTTPException +from sqlmodel import Session, select + +from djqs.api.engines import EngineInfo +from djqs.api.helpers import get_catalog, get_engine +from djqs.exceptions import DJException +from djqs.models.catalog import Catalog, CatalogInfo +from djqs.models.engine import BaseEngineInfo +from djqs.utils import get_session + +_logger = logging.getLogger(__name__) +router = APIRouter(tags=["Catalogs & Engines"]) + + +@router.get("/catalogs/", response_model=List[CatalogInfo]) +def list_catalogs(*, session: Session = Depends(get_session)) -> List[CatalogInfo]: + """ + List all available catalogs + """ + return list(session.exec(select(Catalog))) + + +@router.get("/catalogs/{name}/", response_model=CatalogInfo) +def read_catalog(name: str, *, session: Session = Depends(get_session)) -> CatalogInfo: + """ + Return a catalog by name + """ + return get_catalog(session, name) + + +@router.post("/catalogs/", response_model=CatalogInfo, status_code=201) +def add_catalog( + data: CatalogInfo, + *, + session: Session = Depends(get_session), +) -> CatalogInfo: + """ + Add a Catalog + """ + try: + get_catalog(session, data.name) + except DJException: + pass + else: + raise HTTPException( + status_code=HTTPStatus.CONFLICT, + detail=f"Catalog already exists: `{data.name}`", + ) + + catalog = Catalog.from_orm(data) + catalog.engines.extend( + list_new_engines( + session=session, + catalog=catalog, + create_engines=data.engines, + ), + ) + session.add(catalog) + session.commit() + session.refresh(catalog) + + return catalog + + +@router.post("/catalogs/{name}/engines/", response_model=CatalogInfo, status_code=201) +def add_engines_to_catalog( + name: str, + data: List[BaseEngineInfo], + *, + session: Session = Depends(get_session), +) -> CatalogInfo: + """ + Attach one or more engines to a catalog + """ + catalog = get_catalog(session, name) + catalog.engines.extend( + list_new_engines(session=session, catalog=catalog, create_engines=data), + ) + session.add(catalog) + session.commit() + session.refresh(catalog) + return catalog + + +def list_new_engines( + session: Session, + catalog: Catalog, + create_engines: List[EngineInfo], +) -> List[EngineInfo]: + """ + Filter to engines that are not already set on a catalog + """ + new_engines = [] + for engine_ref in create_engines: + already_set = False + engine = get_engine(session, engine_ref.name, engine_ref.version) + for set_engine in catalog.engines: + if engine.name == set_engine.name and engine.version == set_engine.version: + already_set = True + if not already_set: + new_engines.append(engine) + return new_engines diff --git a/datajunction-query/djqs/api/engines.py b/datajunction-query/djqs/api/engines.py new file mode 100644 index 000000000..2c1be5d0f --- /dev/null +++ b/datajunction-query/djqs/api/engines.py @@ -0,0 +1,60 @@ +""" +Engine related APIs. +""" + +from http import HTTPStatus +from typing import List + +from fastapi import APIRouter, Depends, HTTPException +from sqlmodel import Session, select + +from djqs.api.helpers import get_engine +from djqs.models.engine import BaseEngineInfo, Engine, EngineInfo +from djqs.utils import get_session + +router = APIRouter(tags=["Catalogs & Engines"]) + + +@router.get("/engines/", response_model=List[EngineInfo]) +def list_engines(*, session: Session = Depends(get_session)) -> List[EngineInfo]: + """ + List all available engines + """ + return list(session.exec(select(Engine))) + + +@router.get("/engines/{name}/{version}/", response_model=BaseEngineInfo) +def list_engine( + name: str, version: str, *, session: Session = Depends(get_session) +) -> BaseEngineInfo: + """ + Return an engine by name and version + """ + return get_engine(session, name, version) + + +@router.post("/engines/", response_model=BaseEngineInfo, status_code=201) +def add_engine( + data: EngineInfo, + *, + session: Session = Depends(get_session), +) -> BaseEngineInfo: + """ + Add an Engine + """ + try: + get_engine(session, data.name, data.version) + except HTTPException: + pass + else: + raise HTTPException( + status_code=HTTPStatus.CONFLICT, + detail=f"Engine already exists: `{data.name}` version `{data.version}`", + ) + + engine = Engine.from_orm(data) + session.add(engine) + session.commit() + session.refresh(engine) + + return engine diff --git a/datajunction-query/djqs/api/helpers.py b/datajunction-query/djqs/api/helpers.py new file mode 100644 index 000000000..735d61008 --- /dev/null +++ b/datajunction-query/djqs/api/helpers.py @@ -0,0 +1,89 @@ +""" +Helper functions for API +""" +from http import HTTPStatus +from typing import Any, Dict, List, Optional + +from fastapi import HTTPException +from sqlalchemy import inspect +from sqlalchemy.exc import NoResultFound, NoSuchTableError, OperationalError +from sqlmodel import Session, create_engine, select + +from djqs.engine import describe_table_via_spark, get_spark_session +from djqs.exceptions import DJException +from djqs.models.catalog import Catalog +from djqs.models.engine import Engine + + +def get_catalog(session: Session, name: str) -> Catalog: + """ + Get a catalog by name + """ + statement = select(Catalog).where(Catalog.name == name) + catalog = session.exec(statement).one_or_none() + if not catalog: + raise DJException( + message=f"Catalog with name `{name}` does not exist.", + http_status_code=404, + ) + return catalog + + +def get_engine(session: Session, name: str, version: str) -> Engine: + """ + Return an Engine instance given an engine name and version + """ + statement = ( + select(Engine).where(Engine.name == name).where(Engine.version == version) + ) + try: + engine = session.exec(statement).one() + except NoResultFound as exc: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, + detail=f"Engine not found: `{name}` version `{version}`", + ) from exc + return engine + + +def get_columns( + table: str, + schema: Optional[str], + catalog: Optional[str], + uri: Optional[str], + extra_params: Optional[Dict[str, Any]], +) -> List[Dict[str, str]]: # pragma: no cover + """ + Return all columns in a given table. + """ + if not uri: + raise DJException("Cannot retrieve columns without a uri") + + if uri.startswith("spark://"): + spark = get_spark_session() + return describe_table_via_spark( + spark, + schema, + table, + ) + + engine = create_engine(uri, **extra_params) + try: + inspector = inspect(engine) + column_metadata = inspector.get_columns( + table, + schema=schema, + ) + except NoSuchTableError as exc: # pylint: disable=broad-except + raise DJException( + message=f"No such table `{table}` in schema `{schema}` in catalog `{catalog}`", + ) from exc + except OperationalError as exc: + if "unknown database" in str(exc): + raise DJException(message=f"No such schema `{schema}`") from exc + raise + + return [ + {"name": column["name"], "type": column["type"].python_type.__name__.upper()} + for column in column_metadata + ] diff --git a/datajunction-query/djqs/api/main.py b/datajunction-query/djqs/api/main.py new file mode 100644 index 000000000..8fe07408a --- /dev/null +++ b/datajunction-query/djqs/api/main.py @@ -0,0 +1,51 @@ +""" +Main DJ query server app. +""" + +# All the models need to be imported here so that SQLModel can define their +# relationships at runtime without causing circular imports. +# See https://sqlmodel.tiangolo.com/tutorial/code-structure/#make-circular-imports-work. +# pylint: disable=unused-import + +import logging + +from fastapi import FastAPI, Request +from fastapi.responses import JSONResponse + +from djqs import __version__ +from djqs.api import catalogs, engines, queries, tables +from djqs.exceptions import DJException +from djqs.utils import get_settings + +_logger = logging.getLogger(__name__) + + +settings = get_settings() +app = FastAPI( + title=settings.name, + description=settings.description, + version=__version__, + license_info={ + "name": "MIT License", + "url": "https://mit-license.org/", + }, +) +app.include_router(catalogs.router) +app.include_router(engines.router) +app.include_router(queries.router) +app.include_router(tables.router) + + +@app.exception_handler(DJException) +async def dj_exception_handler( # pylint: disable=unused-argument + request: Request, + exc: DJException, +) -> JSONResponse: + """ + Capture errors and return JSON. + """ + return JSONResponse( # pragma: no cover + status_code=exc.http_status_code, + content=exc.to_dict(), + headers={"X-DJ-Error": "true", "X-DBAPI-Exception": exc.dbapi_exception}, + ) diff --git a/datajunction-query/djqs/api/queries.py b/datajunction-query/djqs/api/queries.py new file mode 100644 index 000000000..2fb30ec99 --- /dev/null +++ b/datajunction-query/djqs/api/queries.py @@ -0,0 +1,203 @@ +""" +Query related APIs. +""" +import json +import logging +import uuid +from http import HTTPStatus +from typing import Any, List, Optional + +import msgpack +from accept_types import get_best_match +from fastapi import ( + APIRouter, + BackgroundTasks, + Body, + Depends, + Header, + HTTPException, + Request, + Response, +) +from sqlmodel import Session + +from djqs.config import Settings +from djqs.engine import process_query +from djqs.models.query import ( + Query, + QueryCreate, + QueryResults, + QueryState, + Results, + StatementResults, + decode_results, + encode_results, +) +from djqs.utils import get_session, get_settings + +_logger = logging.getLogger(__name__) +router = APIRouter(tags=["SQL Queries"]) + + +@router.post( + "/queries/", + response_model=QueryResults, + status_code=HTTPStatus.OK, + responses={ + 200: { + "content": {"application/msgpack": {}}, + "description": "Return results as JSON or msgpack", + }, + }, + openapi_extra={ + "requestBody": { + "content": { + "application/json": { + "schema": QueryCreate.schema( + ref_template="#/components/schemas/{model}", + ), + }, + "application/msgpack": { + "schema": QueryCreate.schema( + ref_template="#/components/schemas/{model}", + ), + }, + }, + }, + }, +) +async def submit_query( + accept: Optional[str] = Header(None), + *, + session: Session = Depends(get_session), + settings: Settings = Depends(get_settings), + request: Request, + response: Response, + background_tasks: BackgroundTasks, + body: Any = Body(...), +) -> QueryResults: + """ + Run or schedule a query. + + This endpoint is different from others in that it accepts both JSON and msgpack, and + can also return JSON or msgpack, depending on HTTP headers. + """ + content_type = request.headers.get("content-type") + if content_type == "application/json": + data = body + elif content_type == "application/msgpack": + data = msgpack.unpackb(body, ext_hook=decode_results) + elif content_type is None: + raise HTTPException( + status_code=HTTPStatus.BAD_REQUEST, + detail="Content type must be specified", + ) + else: + raise HTTPException( + status_code=HTTPStatus.UNPROCESSABLE_ENTITY, + detail=f"Content type not accepted: {content_type}", + ) + create_query = QueryCreate(**data) + + query_with_results = save_query_and_run( + create_query, + session, + settings, + response, + background_tasks, + ) + + return_type = get_best_match(accept, ["application/json", "application/msgpack"]) + if not return_type: + raise HTTPException( + status_code=HTTPStatus.NOT_ACCEPTABLE, + detail="Client MUST accept: application/json, application/msgpack", + ) + + if return_type == "application/msgpack": + content = msgpack.packb( + query_with_results.dict(by_alias=True), + default=encode_results, + ) + else: + content = query_with_results.json(by_alias=True) + + return Response( + content=content, + media_type=return_type, + status_code=response.status_code or HTTPStatus.OK, + ) + + +def save_query_and_run( + create_query: QueryCreate, + session: Session, + settings: Settings, + response: Response, + background_tasks: BackgroundTasks, +) -> QueryResults: + """ + Store a new query to the DB and run it. + """ + query = Query(**create_query.dict(by_alias=True)) + query.state = QueryState.ACCEPTED + + session.add(query) + session.commit() + session.refresh(query) + + if query.async_: + background_tasks.add_task(process_query, session, settings, query) + + response.status_code = HTTPStatus.CREATED + return QueryResults(results=[], errors=[], **query.dict()) + + return process_query(session, settings, query) + + +def load_query_results( + settings: Settings, + key: str, +) -> List[StatementResults]: + """ + Load results from backend, if available. + + If ``paginate`` is true we also load the results into the cache, anticipating more + paginated queries. + """ + if settings.results_backend.has(key): + _logger.info("Reading results from results backend") + cached = settings.results_backend.get(key) + query_results = json.loads(cached) + else: + _logger.warning("No results found") + query_results = [] + + return query_results + + +@router.get("/queries/{query_id}/", response_model=QueryResults) +def read_query( + query_id: uuid.UUID, + *, + session: Session = Depends(get_session), + settings: Settings = Depends(get_settings), +) -> QueryResults: + """ + Fetch information about a query. + + For paginated queries we move the data from the results backend to the cache for a + short period, anticipating additional requests. + """ + query = session.get(Query, query_id) + if not query: + raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Query not found") + + query_results = load_query_results(settings, str(query_id)) + + prev = next_ = None + results = Results(__root__=query_results) + + return QueryResults( + results=results, next=next_, previous=prev, errors=[], **query.dict() + ) diff --git a/datajunction-query/djqs/api/tables.py b/datajunction-query/djqs/api/tables.py new file mode 100644 index 000000000..56f87bf79 --- /dev/null +++ b/datajunction-query/djqs/api/tables.py @@ -0,0 +1,51 @@ +""" +Table related APIs. +""" +from typing import Optional + +from fastapi import APIRouter, Depends +from sqlmodel import Session + +from djqs.api.helpers import get_columns, get_engine +from djqs.exceptions import DJInvalidTableRef +from djqs.models.table import TableInfo +from djqs.utils import get_session, get_settings + +router = APIRouter(tags=["Table Reflection"]) + + +@router.get("/table/{table}/columns/", response_model=TableInfo) +def table_columns( + table: str, + engine: Optional[str] = None, + engine_version: Optional[str] = None, + *, + session: Session = Depends(get_session), +) -> TableInfo: + """ + Get column information for a table + """ + table_parts = table.split(".") + if len(table_parts) != 3: + raise DJInvalidTableRef( + http_status_code=422, + message=f"The provided table value `{table}` is invalid. A valid value " + f"for `table` must be in the format `..`", + ) + settings = get_settings() + engine = get_engine( + session=session, + name=engine or settings.default_reflection_engine, + version=engine_version or settings.default_reflection_engine_version, + ) + external_columns = get_columns( + uri=engine.uri, + extra_params={}, + catalog=table_parts[0], + schema=table_parts[1], + table=table_parts[2], + ) + return TableInfo( + name=table, + columns=external_columns, + ) diff --git a/datajunction-query/djqs/config.py b/datajunction-query/djqs/config.py new file mode 100644 index 000000000..26c5a78ba --- /dev/null +++ b/datajunction-query/djqs/config.py @@ -0,0 +1,36 @@ +""" +Configuration for the query service +""" + +from datetime import timedelta + +from cachelib.base import BaseCache +from cachelib.file import FileSystemCache +from pydantic import BaseSettings + + +class Settings(BaseSettings): # pylint: disable=too-few-public-methods + """ + Configuration for the query service + """ + + name: str = "DJQS" + description: str = "A DataJunction Query Service" + url: str = "http://localhost:8001/" + + # SQLAlchemy URI for the metadata database. + index: str = "sqlite:///djqs.db?check_same_thread=False" + + # The default engine to use for reflection + default_reflection_engine: str = "default" + + # The default engine version to use for reflection + default_reflection_engine_version: str = "" + + # Where to store the results from queries. + results_backend: BaseCache = FileSystemCache("/tmp/djqs", default_timeout=0) + + paginating_timeout: timedelta = timedelta(minutes=5) + + # How long to wait when pinging databases to find out the fastest online database. + do_ping_timeout: timedelta = timedelta(seconds=5) diff --git a/datajunction-query/djqs/constants.py b/datajunction-query/djqs/constants.py new file mode 100644 index 000000000..46f9510c0 --- /dev/null +++ b/datajunction-query/djqs/constants.py @@ -0,0 +1,17 @@ +""" +Useful constants. +""" + +from datetime import timedelta +from uuid import UUID + +DJ_DATABASE_ID = 0 +DJ_DATABASE_UUID = UUID("594804bf-47cb-426c-83c4-94a348e95972") +SQLITE_DATABASE_ID = -1 +SQLITE_DATABASE_UUID = UUID("3619eeba-d628-4ab1-9dd5-65738ab3c02f") + +DEFAULT_DIMENSION_COLUMN = "id" + +# used by the SQLAlchemy client +QUERY_EXECUTE_TIMEOUT = timedelta(seconds=60) +GET_COLUMNS_TIMEOUT = timedelta(seconds=60) diff --git a/datajunction-query/djqs/engine.py b/datajunction-query/djqs/engine.py new file mode 100644 index 000000000..687edadc2 --- /dev/null +++ b/datajunction-query/djqs/engine.py @@ -0,0 +1,214 @@ +""" +Query related functions. +""" + +import logging +from datetime import datetime, timezone +from typing import List, Optional, Tuple + +import duckdb +import sqlparse +from pyspark.sql import SparkSession # pylint: disable=import-error +from sqlalchemy import create_engine, text +from sqlmodel import Session, select + +from djqs.config import Settings +from djqs.models.catalog import Catalog +from djqs.models.engine import Engine +from djqs.models.query import ( + ColumnMetadata, + Query, + QueryResults, + QueryState, + Results, + StatementResults, +) +from djqs.typing import ColumnType, Description, SQLADialect, Stream, TypeEnum + +_logger = logging.getLogger(__name__) + + +def get_columns_from_description( + description: Description, + dialect: SQLADialect, +) -> List[ColumnMetadata]: + """ + Extract column metadata from the cursor description. + + For now this uses the information from the cursor description, which only allow us to + distinguish between 4 types (see ``TypeEnum``). In the future we should use a type + inferrer to determine the types based on the query. + """ + type_map = { + TypeEnum.STRING: ColumnType.STR, + TypeEnum.BINARY: ColumnType.BYTES, + TypeEnum.NUMBER: ColumnType.FLOAT, + TypeEnum.DATETIME: ColumnType.DATETIME, + } + + columns = [] + for column in description or []: + name, native_type = column[:2] + for dbapi_type in TypeEnum: + if native_type == getattr( + dialect.dbapi, + dbapi_type.value, + None, + ): # pragma: no cover + type_ = type_map[dbapi_type] + break + else: + # fallback to string + type_ = ColumnType.STR # pragma: no cover + + columns.append(ColumnMetadata(name=name, type=type_)) + + return columns + + +def run_query( + session: Session, + query: Query, +) -> List[Tuple[str, List[ColumnMetadata], Stream]]: + """ + Run a query and return its results. + + For each statement we return a tuple with the statement SQL, a description of the + columns (name and type) and a stream of rows (tuples). + """ + _logger.info("Running query on catalog %s", query.catalog_name) + catalog = session.exec( + select(Catalog).where(Catalog.name == query.catalog_name), + ).one() + engine = session.exec( + select(Engine) + .where(Engine.name == query.engine_name) + .where(Engine.version == query.engine_version), + ).one() + if engine.uri == "spark://local[*]": + spark = get_spark_session() + return run_spark_query(query, spark) + if engine.uri == "duckdb://local[*]": + conn = duckdb.connect(database="/code/docker/default.duckdb", read_only=False) + return run_duckdb_query(query, conn) + sqla_engine = create_engine(engine.uri, **catalog.extra_params) + connection = sqla_engine.connect() + + output: List[Tuple[str, List[ColumnMetadata], Stream]] = [] + statements = sqlparse.parse(query.executed_query) + for statement in statements: + # Druid doesn't like statements that end in a semicolon... + sql = str(statement).strip().rstrip(";") + + results = connection.execute(text(sql)) + stream = (tuple(row) for row in results) + columns = get_columns_from_description( + results.cursor.description, + sqla_engine.dialect, + ) + output.append((sql, columns, stream)) + + return output + + +def get_spark_session(): + """ + Get a spark session + """ + SparkSession._instantiatedContext = None # pylint: disable=protected-access + spark = ( + SparkSession.builder.master("local[*]") + .appName("djqs") + .enableHiveSupport() + .getOrCreate() + ) + return spark + + +def run_spark_query( + query: Query, + spark: SparkSession, +) -> List[Tuple[str, List[ColumnMetadata], Stream]]: + """ + Run a spark SQL query against the local warehouse + """ + output: List[Tuple[str, List[ColumnMetadata], Stream]] = [] + results_df = spark.sql(query.submitted_query) + rows = results_df.rdd.map(tuple).collect() + columns: List[ColumnMetadata] = [] + output.append((query.submitted_query, columns, rows)) + return output + + +def describe_table_via_spark( + spark: SparkSession, + schema: Optional[str], + table: str, +): + """ + Gets the column schemas. + """ + schema_ = f"{schema}." if schema else "" + schema_df = spark.sql(f"DESCRIBE TABLE {schema_}{table};") + rows = schema_df.rdd.map(tuple).collect() + return [{"name": row[0], "type": row[1]} for row in rows] + + +def run_duckdb_query( + query: Query, + conn: duckdb.DuckDBPyConnection, +) -> List[Tuple[str, List[ColumnMetadata], Stream]]: + """ + Run a duckdb query against the local duckdb database + """ + output: List[Tuple[str, List[ColumnMetadata], Stream]] = [] + rows = conn.execute(query.submitted_query).fetchall() + columns: List[ColumnMetadata] = [] + output.append((query.submitted_query, columns, rows)) + return output + + +def process_query( + session: Session, + settings: Settings, + query: Query, +) -> QueryResults: + """ + Process a query. + """ + query.scheduled = datetime.now(timezone.utc) + query.state = QueryState.SCHEDULED + query.executed_query = query.submitted_query + + errors = [] + query.started = datetime.now(timezone.utc) + try: + root = [] + for sql, columns, stream in run_query(session=session, query=query): + rows = list(stream) + root.append( + StatementResults( + sql=sql, + columns=columns, + rows=rows, + row_count=len(rows), + ), + ) + results = Results(__root__=root) + + query.state = QueryState.FINISHED + query.progress = 1.0 + except Exception as ex: # pylint: disable=broad-except + results = Results(__root__=[]) + query.state = QueryState.FAILED + errors = [str(ex)] + + query.finished = datetime.now(timezone.utc) + + session.add(query) + session.commit() + session.refresh(query) + + settings.results_backend.add(str(query.id), results.json()) + + return QueryResults(results=results, errors=errors, **query.dict()) diff --git a/datajunction-query/djqs/exceptions.py b/datajunction-query/djqs/exceptions.py new file mode 100644 index 000000000..e1ec5e913 --- /dev/null +++ b/datajunction-query/djqs/exceptions.py @@ -0,0 +1,213 @@ +""" +Errors and warnings. +""" + +from enum import Enum +from typing import Any, Dict, List, Literal, Optional, TypedDict + +from sqlmodel import SQLModel + + +class ErrorCode(int, Enum): + """ + Error codes. + """ + + # generic errors + UNKWNON_ERROR = 0 + NOT_IMPLEMENTED_ERROR = 1 + ALREADY_EXISTS = 2 + + # metric API + INVALID_FILTER_PATTERN = 100 + INVALID_COLUMN_IN_FILTER = 101 + INVALID_VALUE_IN_FILTER = 102 + + # SQL API + INVALID_ARGUMENTS_TO_FUNCTION = 200 + + +class DebugType(TypedDict, total=False): + """ + Type for debug information. + """ + + # link to where an issue can be filed + issue: str + + # link to documentation about the problem + documentation: str + + # any additional context + context: Dict[str, Any] + + +class DJErrorType(TypedDict): + """ + Type for serialized errors. + """ + + code: int + message: str + debug: Optional[DebugType] + + +class DJError(SQLModel): + """ + An error. + """ + + code: ErrorCode + message: str + debug: Optional[Dict[str, Any]] + + def __str__(self) -> str: + """ + Format the error nicely. + """ + return f"{self.message} (error code: {self.code})" + + +class DJWarningType(TypedDict): + """ + Type for serialized warnings. + """ + + code: Optional[int] + message: str + debug: Optional[DebugType] + + +class DJWarning(SQLModel): + """ + A warning. + """ + + code: Optional[ErrorCode] = None + message: str + debug: Optional[Dict[str, Any]] + + +DBAPIExceptions = Literal[ + "Warning", + "Error", + "InterfaceError", + "DatabaseError", + "DataError", + "OperationalError", + "IntegrityError", + "InternalError", + "ProgrammingError", + "NotSupportedError", +] + + +class DJExceptionType(TypedDict): + """ + Type for serialized exceptions. + """ + + message: Optional[str] + errors: List[DJErrorType] + warnings: List[DJWarningType] + + +class DJException(Exception): + """ + Base class for errors. + """ + + message: str + errors: List[DJError] + warnings: List[DJWarning] + + # exception that should be raised when ``DJException`` is caught by the DB API cursor + dbapi_exception: DBAPIExceptions = "Error" + + # status code that should be returned when ``DJException`` is caught by the API layer + http_status_code: int = 500 + + def __init__( # pylint: disable=too-many-arguments + self, + message: Optional[str] = None, + errors: Optional[List[DJError]] = None, + warnings: Optional[List[DJWarning]] = None, + dbapi_exception: Optional[DBAPIExceptions] = None, + http_status_code: Optional[int] = None, + ): + self.errors = errors or [] + self.warnings = warnings or [] + self.message = message or "\n".join(error.message for error in self.errors) + + if dbapi_exception is not None: + self.dbapi_exception = dbapi_exception + if http_status_code is not None: + self.http_status_code = http_status_code + + super().__init__(self.message) + + def to_dict(self) -> DJExceptionType: + """ + Convert to dict. + """ + return { # pragma: no cover + "message": self.message, + "errors": [error.dict() for error in self.errors], + "warnings": [warning.dict() for warning in self.warnings], + } + + def __str__(self) -> str: + """ + Format the exception nicely. + """ + if not self.errors: + return self.message + + plural = "s" if len(self.errors) > 1 else "" + combined_errors = "\n".join(f"- {error}" for error in self.errors) + errors = f"The following error{plural} happened:\n{combined_errors}" + + return f"{self.message}\n{errors}" + + def __eq__(self, other) -> bool: + return ( # pragma: no cover + isinstance(other, DJException) + and self.message == other.message + and self.errors == other.errors + and self.warnings == other.warnings + and self.dbapi_exception == other.dbapi_exception + and self.http_status_code == other.http_status_code + ) + + +class DJInvalidInputException(DJException): + """ + Exception raised when the input provided by the user is invalid. + """ + + dbapi_exception: DBAPIExceptions = "ProgrammingError" + http_status_code: int = 422 + + +class DJNotImplementedException(DJException): + """ + Exception raised when some functionality hasn't been implemented in DJ yet. + """ + + dbapi_exception: DBAPIExceptions = "NotSupportedError" + http_status_code: int = 500 + + +class DJInternalErrorException(DJException): + """ + Exception raised when we do something wrong in the code. + """ + + dbapi_exception: DBAPIExceptions = "InternalError" + http_status_code: int = 500 + + +class DJInvalidTableRef(DJException): + """ + Raised for invalid table values + """ diff --git a/datajunction-query/djqs/fixes.py b/datajunction-query/djqs/fixes.py new file mode 100644 index 000000000..2d348e839 --- /dev/null +++ b/datajunction-query/djqs/fixes.py @@ -0,0 +1,3 @@ +""" +Database-specific fixes. +""" diff --git a/datajunction-query/djqs/models/__init__.py b/datajunction-query/djqs/models/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/datajunction-query/djqs/models/catalog.py b/datajunction-query/djqs/models/catalog.py new file mode 100644 index 000000000..10e231ea9 --- /dev/null +++ b/datajunction-query/djqs/models/catalog.py @@ -0,0 +1,75 @@ +""" +Models for columns. +""" +from datetime import datetime, timezone +from functools import partial +from typing import TYPE_CHECKING, Dict, List, Optional +from uuid import UUID, uuid4 + +from sqlalchemy import DateTime +from sqlalchemy.sql.schema import Column as SqlaColumn +from sqlalchemy_utils import UUIDType +from sqlmodel import JSON, Field, Relationship, SQLModel + +from djqs.models.engine import BaseEngineInfo, Engine + +if TYPE_CHECKING: + from djqs.utils import UTCDatetime + + +class CatalogEngines(SQLModel, table=True): # type: ignore + """ + Join table for catalogs and engines. + """ + + catalog_id: Optional[int] = Field( + default=None, + foreign_key="catalog.id", + primary_key=True, + ) + engine_id: Optional[int] = Field( + default=None, + foreign_key="engine.id", + primary_key=True, + ) + + +class Catalog(SQLModel, table=True): # type: ignore + """ + A catalog. + """ + + id: Optional[int] = Field(default=None, primary_key=True) + uuid: UUID = Field(default_factory=uuid4, sa_column=SqlaColumn(UUIDType())) + name: str + engines: List[Engine] = Relationship( + link_model=CatalogEngines, + sa_relationship_kwargs={ + "primaryjoin": "Catalog.id==CatalogEngines.catalog_id", + "secondaryjoin": "Engine.id==CatalogEngines.engine_id", + }, + ) + created_at: "UTCDatetime" = Field( + sa_column=SqlaColumn(DateTime(timezone=True)), + default_factory=partial(datetime.now, timezone.utc), + ) + updated_at: "UTCDatetime" = Field( + sa_column=SqlaColumn(DateTime(timezone=True)), + default_factory=partial(datetime.now, timezone.utc), + ) + extra_params: Dict = Field(default={}, sa_column=SqlaColumn(JSON)) + + def __str__(self) -> str: + return self.name # pragma: no cover + + def __hash__(self) -> int: + return hash(self.id) # pragma: no cover + + +class CatalogInfo(SQLModel): + """ + Class for catalog creation + """ + + name: str + engines: List[BaseEngineInfo] = [] diff --git a/datajunction-query/djqs/models/engine.py b/datajunction-query/djqs/models/engine.py new file mode 100644 index 000000000..3f3d5de5d --- /dev/null +++ b/datajunction-query/djqs/models/engine.py @@ -0,0 +1,35 @@ +""" +Models for columns. +""" + +from typing import Optional + +from sqlmodel import Field, SQLModel + + +class Engine(SQLModel, table=True): # type: ignore + """ + A query engine. + """ + + id: Optional[int] = Field(default=None, primary_key=True) + name: str + version: str + uri: Optional[str] + + +class BaseEngineInfo(SQLModel): + """ + Class for engine creation + """ + + name: str + version: str + + +class EngineInfo(BaseEngineInfo): + """ + Class for engine creation + """ + + uri: str diff --git a/datajunction-query/djqs/models/query.py b/datajunction-query/djqs/models/query.py new file mode 100644 index 000000000..283522868 --- /dev/null +++ b/datajunction-query/djqs/models/query.py @@ -0,0 +1,153 @@ +""" +Models for queries. +""" + +import uuid +from datetime import datetime +from enum import Enum +from typing import Any, List, Optional +from uuid import UUID, uuid4 + +import msgpack +from pydantic import AnyHttpUrl +from sqlalchemy.sql.schema import Column as SqlaColumn +from sqlalchemy_utils import UUIDType +from sqlmodel import Field, SQLModel + +from djqs.typing import QueryState, Row + + +class BaseQuery(SQLModel): + """ + Base class for query models. + """ + + catalog_name: Optional[str] + engine_name: Optional[str] = None + engine_version: Optional[str] = None + + class Config: # pylint: disable=too-few-public-methods, missing-class-docstring + allow_population_by_field_name = True + + +class Query(BaseQuery, table=True): # type: ignore + """ + A query. + """ + + id: UUID = Field( + default_factory=uuid4, + sa_column=SqlaColumn(UUIDType(), primary_key=True), + ) + submitted_query: str + catalog_name: str + engine_name: str + engine_version: str + async_: bool + executed_query: Optional[str] = None + scheduled: Optional[datetime] = None + started: Optional[datetime] = None + finished: Optional[datetime] = None + + state: QueryState = QueryState.UNKNOWN + progress: float = 0.0 + + +class QueryCreate(BaseQuery): + """ + Model for submitted queries. + """ + + submitted_query: str + async_: bool = False + + +class ColumnMetadata(SQLModel): + """ + A simple model for column metadata. + """ + + name: str + type: str + + +class StatementResults(SQLModel): + """ + Results for a given statement. + + This contains the SQL, column names and types, and rows + """ + + sql: str + columns: List[ColumnMetadata] + rows: List[Row] + + # this indicates the total number of rows, and is useful for paginated requests + row_count: int = 0 + + +class Results(SQLModel): + """ + Results for a given query. + """ + + __root__: List[StatementResults] + + +class QueryResults(BaseQuery): + """ + Model for query with results. + """ + + id: uuid.UUID + engine_name: Optional[str] = None + engine_version: Optional[str] = None + submitted_query: str + executed_query: Optional[str] = None + + scheduled: Optional[datetime] = None + started: Optional[datetime] = None + finished: Optional[datetime] = None + + state: QueryState = QueryState.UNKNOWN + progress: float = 0.0 + + results: Results + next: Optional[AnyHttpUrl] = None + previous: Optional[AnyHttpUrl] = None + errors: List[str] + + +class QueryExtType(int, Enum): + """ + Custom ext type for msgpack. + """ + + UUID = 1 + DATETIME = 2 + + +def encode_results(obj: Any) -> Any: + """ + Custom msgpack encoder for ``QueryWithResults``. + """ + if isinstance(obj, uuid.UUID): + return msgpack.ExtType(QueryExtType.UUID, str(obj).encode("utf-8")) + + if isinstance(obj, datetime): + return msgpack.ExtType(QueryExtType.DATETIME, obj.isoformat().encode("utf-8")) + + return obj # pragma: no cover + + +def decode_results(code: int, data: bytes) -> Any: + """ + Custom msgpack decoder for ``QueryWithResults``. + """ + if code == QueryExtType.UUID: + return uuid.UUID(data.decode()) + + if code == QueryExtType.DATETIME: + return datetime.fromisoformat(data.decode()) + + return msgpack.ExtType(code, data) # pragma: no cover diff --git a/datajunction-query/djqs/models/table.py b/datajunction-query/djqs/models/table.py new file mode 100644 index 000000000..c7ecc500f --- /dev/null +++ b/datajunction-query/djqs/models/table.py @@ -0,0 +1,15 @@ +""" +Models for use in table API requests and responses +""" +from typing import Dict, List + +from pydantic import BaseModel + + +class TableInfo(BaseModel): + """ + Table information + """ + + name: str + columns: List[Dict[str, str]] diff --git a/datajunction-query/djqs/typing.py b/datajunction-query/djqs/typing.py new file mode 100644 index 000000000..9f23e17e1 --- /dev/null +++ b/datajunction-query/djqs/typing.py @@ -0,0 +1,325 @@ +""" +Custom types for annotations. +""" + +# pylint: disable=missing-class-docstring + +from __future__ import annotations + +from enum import Enum +from types import ModuleType +from typing import Any, Iterator, List, Literal, Optional, Tuple, TypedDict, Union + +from typing_extensions import Protocol + + +class SQLADialect(Protocol): # pylint: disable=too-few-public-methods + """ + A SQLAlchemy dialect. + """ + + dbapi: ModuleType + + +# The ``type_code`` in a cursor description -- can really be anything +TypeCode = Any + + +# Cursor description +Description = Optional[ + List[ + Tuple[ + str, + TypeCode, + Optional[str], + Optional[str], + Optional[str], + Optional[str], + Optional[bool], + ] + ] +] + + +# A stream of data +Row = Tuple[Any, ...] +Stream = Iterator[Row] + + +class ColumnType(str, Enum): + """ + Types for columns. + + These represent the values from the ``python_type`` attribute in SQLAlchemy columns. + """ + + BYTES = "BYTES" + STR = "STR" + FLOAT = "FLOAT" + INT = "INT" + DECIMAL = "DECIMAL" + BOOL = "BOOL" + DATETIME = "DATETIME" + DATE = "DATE" + TIME = "TIME" + TIMEDELTA = "TIMEDELTA" + LIST = "LIST" + DICT = "DICT" + + +class TypeEnum(str, Enum): + """ + PEP 249 basic types. + + Unfortunately SQLAlchemy doesn't seem to offer an API for determining the types of the + columns in a (SQL Core) query, and the DB API 2.0 cursor only offers very coarse + types. + """ + + STRING = "STRING" + BINARY = "BINARY" + NUMBER = "NUMBER" + DATETIME = "DATETIME" + UNKNOWN = "UNKNOWN" + + +class QueryState(str, Enum): + """ + Different states of a query. + """ + + UNKNOWN = "UNKNOWN" + ACCEPTED = "ACCEPTED" + SCHEDULED = "SCHEDULED" + RUNNING = "RUNNING" + FINISHED = "FINISHED" + CANCELED = "CANCELED" + FAILED = "FAILED" + + +# sqloxide type hints +# Reference: https://github.com/sqlparser-rs/sqlparser-rs/blob/main/src/ast/query.rs + + +class Value(TypedDict, total=False): + Number: Tuple[str, bool] + SingleQuotedString: str + Boolean: bool + + +class Limit(TypedDict): + Value: Value + + +class Identifier(TypedDict): + quote_style: Optional[str] + value: str + + +class Bound(TypedDict, total=False): + Following: int + Preceding: int + + +class WindowFrame(TypedDict): + end_bound: Bound + start_bound: Bound + units: str + + +class Expression(TypedDict, total=False): + CompoundIdentifier: List["Identifier"] + Identifier: Identifier + Value: Value + Function: Function # type: ignore + UnaryOp: UnaryOp # type: ignore + BinaryOp: BinaryOp # type: ignore + Case: Case # type: ignore + + +class Case(TypedDict): + conditions: List[Expression] + else_result: Optional[Expression] + operand: Optional[Expression] + results: List[Expression] + + +class UnnamedArgument(TypedDict): + Expr: Expression + + +class Argument(TypedDict, total=False): + Unnamed: Union[UnnamedArgument, Wildcard] + + +class Over(TypedDict): + order_by: List[Expression] + partition_by: List[Expression] + window_frame: WindowFrame + + +class Function(TypedDict): + args: List[Argument] + distinct: bool + name: List[Identifier] + over: Optional[Over] + + +class ExpressionWithAlias(TypedDict): + alias: Identifier + expr: Expression + + +class Offset(TypedDict): + rows: str + value: Expression + + +class OrderBy(TypedDict, total=False): + asc: Optional[bool] + expr: Expression + nulls_first: Optional[bool] + + +class Projection(TypedDict, total=False): + ExprWithAlias: ExpressionWithAlias + UnnamedExpr: Expression + + +Wildcard = Literal["Wildcard"] + + +class Fetch(TypedDict): + percent: bool + quantity: Value + with_ties: bool + + +Top = Fetch + + +class UnaryOp(TypedDict): + op: str + expr: Expression + + +class BinaryOp(TypedDict): + left: Expression + op: str + right: Expression + + +class LateralView(TypedDict): + lateral_col_alias: List[Identifier] + lateral_view: Expression + lateral_view_name: List[Identifier] + outer: bool + + +class TableAlias(TypedDict): + columns: List[Identifier] + name: Identifier + + +class Table(TypedDict): + alias: Optional[TableAlias] + args: List[Argument] + name: List[Identifier] + with_hints: List[Expression] + + +class Derived(TypedDict): + lateral: bool + subquery: "Body" # type: ignore + alias: Optional[TableAlias] + + +class Relation(TypedDict, total=False): + Table: Table + Derived: Derived + + +class JoinConstraint(TypedDict): + On: Expression + Using: List[Identifier] + + +class JoinOperator(TypedDict, total=False): + Inner: JoinConstraint + LeftOuter: JoinConstraint + RightOuter: JoinConstraint + FullOuter: JoinConstraint + + +CrossJoin = Literal["CrossJoin"] +CrossApply = Literal["CrossApply"] +OuterApply = Literal["Outerapply"] + + +class Join(TypedDict): + join_operator: Union[JoinOperator, CrossJoin, CrossApply, OuterApply] + relation: Relation + + +class From(TypedDict): + joins: List[Join] + relation: Relation + + +Select = TypedDict( + "Select", + { + "cluster_by": List[Expression], + "distinct": bool, + "distribute_by": List[Expression], + "from": List[From], + "group_by": List[Expression], + "having": Optional[BinaryOp], + "lateral_views": List[LateralView], + "projection": List[Union[Projection, Wildcard]], + "selection": Optional[BinaryOp], + "sort_by": List[Expression], + "top": Optional[Top], + }, +) + + +class Body(TypedDict): + Select: Select + + +CTETable = TypedDict( + "CTETable", + { + "alias": TableAlias, + "from": Optional[Identifier], + "query": "Query", # type: ignore + }, +) + + +class With(TypedDict): + cte_tables: List[CTETable] + + +Query = TypedDict( + "Query", + { + "body": Body, + "fetch": Optional[Fetch], + "limit": Optional[Limit], + "lock": Optional[Literal["Share", "Update"]], + "offset": Optional[Offset], + "order_by": List[OrderBy], + "with": Optional[With], + }, +) + + +# We could support more than just ``SELECT`` here. +class Statement(TypedDict): + Query: Query + + +# A parse tree, result of ``sqloxide.parse_sql``. +ParseTree = List[Statement] # type: ignore diff --git a/datajunction-query/djqs/utils.py b/datajunction-query/djqs/utils.py new file mode 100644 index 000000000..69a219730 --- /dev/null +++ b/datajunction-query/djqs/utils.py @@ -0,0 +1,90 @@ +""" +Utility functions. +""" +# pylint: disable=line-too-long + +import datetime +import logging +import os +from functools import lru_cache +from typing import Iterator + +from dotenv import load_dotenv +from pydantic.datetime_parse import parse_datetime +from rich.logging import RichHandler +from sqlalchemy.engine import Engine +from sqlmodel import Session, create_engine + +from djqs.config import Settings + + +def setup_logging(loglevel: str) -> None: + """ + Setup basic logging. + """ + level = getattr(logging, loglevel.upper(), None) + if not isinstance(level, int): + raise ValueError(f"Invalid log level: {loglevel}") + + logformat = "[%(asctime)s] %(levelname)s: %(name)s: %(message)s" + logging.basicConfig( + level=level, + format=logformat, + datefmt="[%X]", + handlers=[RichHandler(rich_tracebacks=True)], + force=True, + ) + + +@lru_cache +def get_settings() -> Settings: + """ + Return a cached settings object. + """ + dotenv_file = os.environ.get("DOTENV_FILE", ".env") + load_dotenv(dotenv_file) + return Settings() + + +def get_metadata_engine() -> Engine: + """ + Create the metadata engine. + """ + settings = get_settings() + engine = create_engine(settings.index) + + return engine + + +def get_session() -> Iterator[Session]: + """ + Per-request session. + """ + engine = get_metadata_engine() + + with Session(engine, autoflush=False) as session: # pragma: no cover + yield session + + +class UTCDatetime(datetime.datetime): # pragma: no cover + """ + A UTC extension of pydantic's normal datetime handling + """ + + @classmethod + def __get_validators__(cls): + """ + Extend the builtin pydantic datetime parser with a custom validate method + """ + yield parse_datetime + yield cls.validate + + @classmethod + def validate(cls, value) -> str: + """ + Convert to UTC + """ + if value.tzinfo is None: + return value.replace(tzinfo=datetime.timezone.utc) + + return value.astimezone(datetime.timezone.utc) diff --git a/datajunction-query/docker/cockroachdb/cockroachdb_examples_init.sql b/datajunction-query/docker/cockroachdb/cockroachdb_examples_init.sql new file mode 100644 index 000000000..6887b966f --- /dev/null +++ b/datajunction-query/docker/cockroachdb/cockroachdb_examples_init.sql @@ -0,0 +1,35 @@ +CREATE DATABASE steam; +CREATE TABLE steam.playtime(user_id VARCHAR, game VARCHAR, behavior VARCHAR, amount FLOAT, zero INT); + +IMPORT INTO steam.playtime (user_id, game, behavior, amount, zero) +CSV DATA ( + 'nodelocal://self/steam-hours-played.csv' +); + +CREATE TABLE steam.games( + url VARCHAR, + types VARCHAR, + name VARCHAR, + desc_snippet VARCHAR, + recent_reviews VARCHAR, + all_reviews VARCHAR, + release_date VARCHAR, + developer VARCHAR, + publisher VARCHAR, + popular_tags VARCHAR, + game_details VARCHAR, + languages VARCHAR, + achievements VARCHAR, + genre VARCHAR, + game_description VARCHAR, + mature_content VARCHAR, + minimum_requirements VARCHAR, + recommended_requirements VARCHAR, + original_price VARCHAR, + discount_price VARCHAR +); + +IMPORT INTO steam.games (url,types,name,desc_snippet,recent_reviews,all_reviews,release_date,developer,publisher,popular_tags,game_details,languages,achievements,genre,game_description,mature_content,minimum_requirements,recommended_requirements,original_price,discount_price) +CSV DATA ( + 'nodelocal://self/steam-games.csv' +); diff --git a/datajunction-query/docker/cockroachdb/cockroachdb_metadata_init.sql b/datajunction-query/docker/cockroachdb/cockroachdb_metadata_init.sql new file mode 100644 index 000000000..3e196982b --- /dev/null +++ b/datajunction-query/docker/cockroachdb/cockroachdb_metadata_init.sql @@ -0,0 +1 @@ +CREATE DATABASE djqs; diff --git a/datajunction-query/docker/cockroachdb/steam-games.csv b/datajunction-query/docker/cockroachdb/steam-games.csv new file mode 100644 index 000000000..da70b3ce4 --- /dev/null +++ b/datajunction-query/docker/cockroachdb/steam-games.csv @@ -0,0 +1,19 @@ +https://store.steampowered.com/app/379720/DOOM/,app,DOOM,"Now includes all three premium DLC packs (Unto the Evil, Hell Followed, and Bloodfall), maps, modes, and weapons, as well as all feature updates including Arcade Mode, Photo Mode, and the latest Update 6.66, which brings further multiplayer improvements as well as revamps multiplayer progression.","Very Positive,(554),- 89% of the 554 user reviews in the last 30 days are positive.","Very Positive,(42,550),- 92% of the 42,550 user reviews for this game are positive.","May 12, 2016",id Software,"Bethesda Softworks,Bethesda Softworks","FPS,Gore,Action,Demons,Shooter,First-Person,Great Soundtrack,Multiplayer,Singleplayer,Fast-Paced,Sci-fi,Horror,Classic,Atmospheric,Difficult,Blood,Remake,Zombies,Co-op,Memes","Single-player,Multi-player,Co-op,Steam Achievements,Steam Trading Cards,Partial Controller Support,Steam Cloud","English,French,Italian,German,Spanish - Spain,Japanese,Polish,Portuguese - Brazil,Russian,Traditional Chinese",54,Action," About This Game Developed by id software, the studio that pioneered the first-person shooter genre and created multiplayer Deathmatch, DOOM returns as a brutally fun and challenging modern-day shooter experience. Relentless demons, impossibly destructive guns, and fast, fluid movement provide the foundation for intense, first-person combat – whether you’re obliterating demon hordes through the depths of Hell in the single-player campaign, or competing against your friends in numerous multiplayer modes. Expand your gameplay experience using DOOM SnapMap game editor to easily create, play, and share your content with the world. STORY: You’ve come here for a reason. The Union Aerospace Corporation’s massive research facility on Mars is overwhelmed by fierce and powerful demons, and only one person stands between their world and ours. As the lone DOOM Marine, you’ve been activated to do one thing – kill them all. KEY FEATURES: A Relentless Campaign There is no taking cover or stopping to regenerate health as you beat back Hell’s raging demon hordes. Combine your arsenal of futuristic and iconic guns, upgrades, movement and an advanced melee system to knock-down, slash, stomp, crush, and blow apart demons in creative and violent ways. Return of id Multiplayer Dominate your opponents in DOOM’s signature, fast-paced arena-style combat. In both classic and all-new game modes, annihilate your enemies utilizing your personal blend of skill, powerful weapons, vertical movement, and unique power-ups that allow you to play as a demon. Endless Possibilities DOOM SnapMap – a powerful, but easy-to-use game and level editor – allows for limitless gameplay experiences on every platform. Without any previous experience or special expertise, any player can quickly and easily snap together and visually customize maps, add pre-defined or completely custom gameplay, and even edit game logic to create new modes. Instantly play your creation, share it with a friend, or make it available to players around the world – all in-game with the push of a button. ",,"Minimum:,OS:,Windows 7/8.1/10 (64-bit versions),Processor:,Intel Core i5-2400/AMD FX-8320 or better,Memory:,8 GB RAM,Graphics:,NVIDIA GTX 670 2GB/AMD Radeon HD 7870 2GB or better,Storage:,55 GB available space,Additional Notes:,Requires Steam activation and broadband internet connection for Multiplayer and SnapMap","Recommended:,OS:,Windows 7/8.1/10 (64-bit versions),Processor:,Intel Core i7-3770/AMD FX-8350 or better,Memory:,8 GB RAM,Graphics:,NVIDIA GTX 970 4GB/AMD Radeon R9 290 4GB or better,Storage:,55 GB available space,Additional Notes:,Requires Steam activation and broadband internet connection for Multiplayer and SnapMap",$19.99,$14.99 +https://store.steampowered.com/app/578080/PLAYERUNKNOWNS_BATTLEGROUNDS/,app,PLAYERUNKNOWN'S BATTLEGROUNDS,PLAYERUNKNOWN'S BATTLEGROUNDS is a battle royale shooter that pits 100 players against each other in a struggle for survival. Gather supplies and outwit your opponents to become the last person standing.,"Mixed,(6,214),- 49% of the 6,214 user reviews in the last 30 days are positive.","Mixed,(836,608),- 49% of the 836,608 user reviews for this game are positive.","Dec 21, 2017",PUBG Corporation,"PUBG Corporation,PUBG Corporation","Survival,Shooter,Multiplayer,Battle Royale,PvP,FPS,Third-Person Shooter,Action,Online Co-Op,Tactical,Co-op,First-Person,Early Access,Strategy,Competitive,Third Person,Team-Based,Difficult,Simulation,Stealth","Multi-player,Online Multi-Player,Stats","English,Korean,Simplified Chinese,French,German,Spanish - Spain,Arabic,Japanese,Polish,Portuguese,Russian,Turkish,Thai,Italian,Portuguese - Brazil,Traditional Chinese,Ukrainian",37,"Action,Adventure,Massively Multiplayer"," About This Game PLAYERUNKNOWN'S BATTLEGROUNDS is a battle royale shooter that pits 100 players against each other in a struggle for survival. Gather supplies and outwit your opponents to become the last person standing. PLAYERUNKNOWN , aka Brendan Greene, is a pioneer of the battle royale genre and the creator of the battle royale game modes in the ARMA series and H1Z1: King of the Kill. At PUBG Corp., Greene is working with a veteran team of developers to make PUBG into the world's premiere battle royale experience."," Mature Content Description The developers describe the content like this: This Game may contain content not appropriate for all ages, or may not be appropriate for viewing at work: Frequent Violence or Gore, General Mature Content ","Minimum:,Requires a 64-bit processor and operating system,OS:,64-bit Windows 7, Windows 8.1, Windows 10,Processor:,Intel Core i5-4430 / AMD FX-6300,Memory:,8 GB RAM,Graphics:,NVIDIA GeForce GTX 960 2GB / AMD Radeon R7 370 2GB,DirectX:,Version 11,Network:,Broadband Internet connection,Storage:,30 GB available space","Recommended:,Requires a 64-bit processor and operating system,OS:,64-bit Windows 7, Windows 8.1, Windows 10,Processor:,Intel Core i5-6600K / AMD Ryzen 5 1600,Memory:,16 GB RAM,Graphics:,NVIDIA GeForce GTX 1060 3GB / AMD Radeon RX 580 4GB,DirectX:,Version 11,Network:,Broadband Internet connection,Storage:,30 GB available space",$29.99, +https://store.steampowered.com/app/637090/BATTLETECH/,app,BATTLETECH,"Take command of your own mercenary outfit of 'Mechs and the MechWarriors that pilot them, struggling to stay afloat as you find yourself drawn into a brutal interstellar civil war.","Mixed,(166),- 54% of the 166 user reviews in the last 30 days are positive.","Mostly Positive,(7,030),- 71% of the 7,030 user reviews for this game are positive.","Apr 24, 2018",Harebrained Schemes,"Paradox Interactive,Paradox Interactive","Mechs,Strategy,Turn-Based,Turn-Based Tactics,Sci-fi,Turn-Based Strategy,Tactical,Singleplayer,Robots,RPG,Action,Multiplayer,Futuristic,Character Customization,Management,Adventure,Space,Story Rich,Great Soundtrack,Difficult","Single-player,Multi-player,Online Multi-Player,Cross-Platform Multiplayer,Steam Achievements,Steam Trading Cards,Steam Cloud","English,French,German,Russian",128,"Action,Adventure,Strategy"," About This Game From original BATTLETECH/MechWarrior creator Jordan Weisman and the developers of the award-winning Shadowrun Returns series comes the next-generation of turn-based tactical 'Mech combat. The year is 3025 and the galaxy is trapped in a cycle of perpetual war, fought by noble houses with enormous, mechanized combat vehicles called BattleMechs. Take command of your own mercenary outfit of 'Mechs and the MechWarriors that pilot them, struggling to stay afloat as you find yourself drawn into a brutal interstellar civil war. Upgrade your starfaring base of operations, negotiate mercenary contracts with feudal lords, repair and maintain your stable of aging BattleMechs, and execute devastating combat tactics to defeat your enemies on the battlefield. COMMAND A SQUAD OF 'MECHS IN TURN-BASED COMBAT Deploy over 30 BattleMechs in a wide variety of combinations. Use terrain, positioning, weapon selection and special abilities to outmaneuver and outplay your opponents. MANAGE YOUR MERCENARY COMPANY Recruit, customize, and develop unique MechWarriors. Improve and customize your dropship. As a Mercenary, travel a wide stretch of space, taking missions and managing your reputation with a variety of noble houses and local factions. TAKE PART IN A DESPERATE CIVIL WAR Immerse yourself in the story of a violently deposed ruler, waging a brutal war to take back her throne with the support of your ragtag mercenary company. CUSTOMIZE YOUR 'MECHS Use your MechLab to maintain and upgrade your units, replacing damaged weapon systems with battlefield salvage taken from fallen foes. PVP MULTIPLAYER & SKIRMISH MODE Customize a Lance of 'Mechs and MechWarriors to go head-to-head with your friends, compete against opponents online, or jump into single-player skirmish mode to test your strategies against the AI. ",,"Minimum:,Requires a 64-bit processor and operating system,OS:,64-bit Windows 7 or Higher,Processor:,Intel® Core™ i3-2105 or AMD® Phenom™ II X3 720,Memory:,8 GB RAM,Graphics:,Nvidia® GeForce™ GTX 560 Ti or AMD® ATI Radeon™ HD 5870 (1 GB VRAM),DirectX:,Version 11,Network:,Broadband Internet connection,Storage:,35 GB available space,Sound Card:,DirectX 9 sound device,Additional Notes:,Multiplayer is compatible between Windows, Mac and Linux versions.,Minimum:,Requires a 64-bit processor and operating system,OS:,macOS High Sierra 10.13.3,Processor:,Intel® Core™ i5-4670,Memory:,8 GB RAM,Graphics:,Nvidia® GeForce™ GTX 775M (2 GB VRAM),Network:,Broadband Internet connection,Storage:,35 GB available space,Additional Notes:,Multiplayer is compatible between Windows, Mac and Linux versions.,Minimum:,Requires a 64-bit processor and operating system,OS:,64-bit Ubuntu 18.04 LTS and higher or SteamOS,Processor:,Intel® Core™ i3-3240 CPU,Memory:,8 GB RAM,Graphics:,Nvidia® GeForce™ GTX 560,Network:,Broadband Internet connection,Storage:,35 GB available space,Additional Notes:,Multiplayer is compatible between Windows, Mac and Linux versions.","Recommended:,Requires a 64-bit processor and operating system,OS:,64-bit Windows 7 or Higher,Processor:,Intel® Core™ i5-4460 or AMD® FX-4300,Memory:,16 GB RAM,Graphics:,Nvidia® GeForce™ GTX 670 or AMD® Radeon™ R9 285 (2 GB VRAM),DirectX:,Version 11,Network:,Broadband Internet connection,Storage:,35 GB available space,Sound Card:,DirectX 9 sound device,Additional Notes:,Multiplayer is compatible between Windows, Mac and Linux versions.,Recommended:,Requires a 64-bit processor and operating system,OS:,macOS High Sierra 10.13.3,Processor:,Intel® Core™ i7-7700K,Memory:,16 GB RAM,Graphics:,AMD® Radeon™ Pro 580 (8 GB VRAM),Network:,Broadband Internet connection,Storage:,35 GB available space,Additional Notes:,Multiplayer is compatible between Windows, Mac and Linux versions.,Recommended:,Requires a 64-bit processor and operating system,OS:,64-bit Ubuntu 18.04 LTS and higher or SteamOS,Processor:,Intel® Core™ i5-4460 or AMD® FX-4300,Memory:,16 GB RAM,Graphics:,Nvidia® GeForce™ GTX 670 or AMD® Radeon™ R9 285 (2 GB VRAM),Network:,Broadband Internet connection,Storage:,35 MB available space,Additional Notes:,Multiplayer is compatible between Windows, Mac and Linux versions.",$39.99, +https://store.steampowered.com/app/221100/DayZ/,app,DayZ,"The post-soviet country of Chernarus is struck by an unknown virus, turning the majority population into frenzied infected. Fighting over resources has bred a hostile mentality among survivors, driving what’s left of humanity to collapse. You are one of the few immune to the virus - how far will you go to survive?","Mixed,(932),- 57% of the 932 user reviews in the last 30 days are positive.","Mixed,(167,115),- 61% of the 167,115 user reviews for this game are positive.","Dec 13, 2018",Bohemia Interactive,"Bohemia Interactive,Bohemia Interactive","Survival,Zombies,Open World,Multiplayer,PvP,Massively Multiplayer,Action,Early Access,Simulation,FPS,Post-apocalyptic,Survival Horror,Shooter,Sandbox,Adventure,Indie,Co-op,Atmospheric,Horror,Military","Multi-player,Online Multi-Player,Steam Workshop,Steam Cloud,Valve Anti-Cheat enabled","English,French,Italian,German,Spanish - Spain,Czech,Russian,Simplified Chinese,Traditional Chinese",NaN,"Action,Adventure,Massively Multiplayer"," About This Game The post-soviet country of Chernarus is struck by an unknown virus, turning the majority population into frenzied infected. Fighting over resources has bred a hostile mentality among survivors, driving what’s left of humanity to collapse. You are one of the few immune to the virus - how far will you go to survive? This is DayZ, this is your story. DayZ is an unforgiving, authentic, open world sandbox online game where each one of 60 players on a server follows a single goal - to survive as long as they can, by all means necessary. There are no superficial tips, waypoints, built-in tutorials or help given to you. Every decision matters - with no save games, and no extra lives, every mistake can be lethal. If you fail, you lose everything and start over. Scavenging for supplies and roaming the open world never feels safe in DayZ, as you never know what's behind the next corner. Hostile player interactions, or simply just struggling through severe weather can easily turn into intense, nerve-racking moments where you experience very real emotions. On the other hand, meeting with another friendly survivor in DayZ can lead to a true friendship that lasts a lifetime... Your choices and your decisions create a gameplay experience that's completely unique and unequivocally personal - unmatched by any other multiplayer game out there. This is DayZ, this is your story. Key Features Detailed, authentic backdrop of Chernarus, an open world terrain featuring 230 square kilometers of hand-crafted environment based on real life locations. Real emotional experience driven by the emergent interactions of 60 players on the server, all fighting for survival by any means necessary. Environmental dangers including the infected, dynamic weather, and animal predators. Wide variety of complex survival mechanics - from hunting and crafting, through sophisticated injury simulation, to transferable diseases. Persistent servers with complex loot economy, and the ability to build improvised bases. Visceral, authentic gun play and melee combat systems. Smooth and reactive character controller utilizing a detailed animation system. Rewarding and authentic experience of driving vehicles for travel and material transport. Robust technology platform featuring modules of Bohemia's new Enfusion Engine. Seamless network synchronization and significantly improved game performance. A platform fully open to user created content, offering the same tool set that we use for actual game development. ",,"Minimum:,OS:,Windows 7/8.1 64-bit,Processor:,Intel Core i5-4430,Memory:,8 GB RAM,Graphics:,NVIDIA GeForce GTX 760 or AMD R9 270X,DirectX:,Version 11,Storage:,16 GB available space,Sound Card:,DirectX®-compatible,Additional Notes:,Internet connection","Recommended:,OS:,Windows 10 64-bit,Processor:,Intel Core i5-6600K or AMD R5 1600X,Memory:,12 GB RAM,Graphics:,NVIDIA GeForce GTX 1060 or AMD RX 580,DirectX:,Version 11,Storage:,25 GB available space,Sound Card:,DirectX®-compatible,Additional Notes:,Internet connection",$44.99, +https://store.steampowered.com/app/8500/EVE_Online/,app,EVE Online,"EVE Online is a community-driven spaceship MMO where players can play free, choosing their own path from countless options. Experience space exploration, immense PvP and PvE battles, mining, industry and a thriving player economy in an ever-expanding sandbox.","Mixed,(287),- 54% of the 287 user reviews in the last 30 days are positive.","Mostly Positive,(11,481),- 74% of the 11,481 user reviews for this game are positive.","May 6, 2003",CCP,"CCP,CCP","Space,Massively Multiplayer,Sci-fi,Sandbox,MMORPG,Open World,RPG,PvP,Multiplayer,Free to Play,Economy,Strategy,Space Sim,Simulation,Action,Difficult,Tactical,Capitalism,PvE,Atmospheric","Multi-player,Online Multi-Player,MMO,Co-op,Online Co-op,Steam Trading Cards","English,German,Russian,French",NaN,"Action,Free to Play,Massively Multiplayer,RPG,Strategy", About This Game ,,"Minimum:,OS:,Windows 7,Processor:,Intel Dual Core @ 2.0 GHz, AMD Dual Core @ 2.0 GHz),Memory:,2 GB,Hard Drive:,20 GB Free Space,Video:,AMD Radeon 2600 XT or NVIDIA GeForce 8600 GTS,Network:,ADSL connection (or faster),Minimum:,Supported OS:,Mac OS X 10.12,Processor:,CPU that supports SSE2 (Intel Dual Core @ 2.0 GHz),Memory:,2 GB,Hard Drive:,20 GB Free Space,Video:,NVIDIA GeForce 320m, Intel HD 3000","Recommended:,OS:,Windows 10,Processor:,Intel i7-7700 or AMD Ryzen 7 1700 @ 3.6 GHz or greater,Memory:,16 GB or greater,Hard Drive:,20 GB free space,Video:,NVIDIA Geforce GTX 1060, AMD Radeon RX 580 or better with at least 4 GB VRAM,Network:,ADSL connection or faster,Recommended:,OS:,Mac OS X 10.14,Processor:,Intel i5 Series @ 3.8 GHz or greater,Memory:,16 GB or higher,Hard Drive:,20 GB free space,Video:,AMD Radeon Pro 580 or better with at least 4 GB VRAM",Free, +https://store.steampowered.com/bundle/5699/Grand_Theft_Auto_V_Premium_Online_Edition/,bundle,Grand Theft Auto V: Premium Online Edition,Grand Theft Auto V: Premium Online Edition bundle,NaN,NaN,NaN,Rockstar North,Rockstar Games,NaN,"Single-player,Multi-player,Downloadable Content,Steam Achievements,Full controller support","English, French, Italian, German, Spanish - Spain, Korean, Polish, Portuguese - Brazil, Russian, Traditional Chinese, Japanese, Simplified Chinese",NaN,"Action,Adventure",NaN,NaN,NaN,NaN,,$35.18 +https://store.steampowered.com/app/601150/Devil_May_Cry_5/,app,Devil May Cry 5,"The ultimate Devil Hunter is back in style, in the game action fans have been waiting for.","Very Positive,(408),- 87% of the 408 user reviews in the last 30 days are positive.","Very Positive,(9,645),- 92% of the 9,645 user reviews for this game are positive.","Mar 7, 2019","CAPCOM Co., Ltd.","CAPCOM Co., Ltd.,CAPCOM Co., Ltd.","Action,Hack and Slash,Great Soundtrack,Demons,Character Action Game,Spectacle fighter,Third Person,Violent,Singleplayer,Classic,Stylized,Gore,Story Rich,Nudity,Multiplayer,Controller,Difficult,Adventure,Anime,Family Friendly","Single-player,Online Multi-Player,Online Co-op,Steam Achievements,Full controller support,Steam Trading Cards,Steam Cloud","English,French,Italian,German,Spanish - Spain,Portuguese - Brazil,Polish,Russian,Simplified Chinese,Traditional Chinese,Japanese,Korean",51,Action," About This Game The Devil you know returns in this brand new entry in the over-the-top action series available on the PC. Prepare to get downright demonic with this signature blend of high-octane stylized action and otherworldly & original characters the series is known for. Director Hideaki Itsuno and the core team have returned to create the most insane, technically advanced and utterly unmissable action experience of this generation! The threat of demonic power has returned to menace the world once again in Devil May Cry 5 . The invasion begins when the seeds of a “demon tree” take root in Red Grave City. As this hellish incursion starts to take over the city, a young demon hunter Nero, arrives with his partner Nico in their “Devil May Cry” motorhome. Finding himself without the use of his right arm, Nero enlists Nico, a self-professed weapons artist, to design a variety of unique mechanical Devil Breaker arms to give him extra powers to take on evil demons such as the blood sucking flying Empusa and giant colossus enemy Goliath. FEATURES High octane stylized action – Featuring three playable characters each with a radically different stylish combat play style as they take on the city overrun with demons Groundbreaking graphics – Developed with Capcom’s in-house proprietary RE engine, the series continues to achieve new heights in fidelity with graphics that utilize photorealistic character designs and stunning lighting and environmental effects. Take down the demonic invasion – Battle against epic bosses in adrenaline fueled fights across the over-run Red Grave City all to the beat of a truly killer soundtrack. Demon hunter – Nero, one of the series main protagonists and a young demon hunter who has the blood of Sparda, heads to Red Grave City to face the hellish onslaught of demons, with weapons craftswoman and new partner-in-crime, Nico. Nero is also joined by stylish, legendary demon hunter, Dante and the mysterious new character, V. "," Mature Content Description The developers describe the content like this: WARNING: This game contains strong language, violence and nudity. ","Minimum:,OS:,WINDOWS® 7, 8.1, 10 (64-BIT Required),Processor:,Intel® Core™ i5-4460, AMD FX™-6300, or better,Memory:,8 GB RAM,Graphics:,NVIDIA® GeForce® GTX 760 or AMD Radeon™ R7 260x with 2GB Video RAM, or better,DirectX:,Version 11,Storage:,35 GB available space,Additional Notes:,*Xinput support Controllers recommended *Internet connection required for game activation. (Network connectivity uses Steam® developed by Valve® Corporation.)","Recommended:,OS:,WINDOWS® 7, 8.1, 10 (64-BIT Required),Processor:,Intel® Core™ i7-3770, AMD FX™-9590, or better,Memory:,8 GB RAM,Graphics:,NVIDIA® GeForce® GTX 1060 with 6GB VRAM, AMD Radeon™ RX 480 with 8GB VRAM, or better,DirectX:,Version 11,Storage:,35 GB available space,Additional Notes:,*Xinput support Controllers recommended *Internet connection required for game activation. (Network connectivity uses Steam® developed by Valve® Corporation.)",$59.99,$70.42 +https://store.steampowered.com/app/477160/Human_Fall_Flat/,app,Human: Fall Flat,Human: Fall Flat is a quirky open-ended physics-based puzzle platformer set in floating dreamscapes. Your goal is to find the exit of these surreal levels by solving puzzles with nothing but your wits. Local co-op for 2 players and up to 8 online for even more mayhem!,"Very Positive,(629),- 91% of the 629 user reviews in the last 30 days are positive.","Very Positive,(23,763),- 91% of the 23,763 user reviews for this game are positive.","Jul 22, 2016",No Brakes Games,"Curve Digital,Curve Digital","Funny,Multiplayer,Co-op,Puzzle,Physics,Local Co-Op,Comedy,Adventure,Indie,Parkour,Puzzle-Platformer,Local Multiplayer,Sandbox,Casual,Open World,Singleplayer,3D Platformer,Split Screen,Simulation,Survival","Single-player,Online Multi-Player,Local Co-op,Shared/Split Screen,Steam Achievements,Full controller support,Steam Trading Cards,Steam Cloud,Stats","English,French,German,Spanish - Spain,Russian,Italian,Simplified Chinese,Japanese,Korean,Polish,Portuguese,Portuguese - Brazil,Thai,Turkish,Ukrainian",55,"Adventure,Indie"," About This Game ***NEW ""DARK"" LEVEL AVAILABLE NOW*** Discover the funniest cooperative physics-based puzzle-platformer! In Human: Fall Flat you play as Bob, a wobbly hero who keeps dreaming about surreal places filled with puzzles in which he’s yet to find the exit. Exploration and ingenuity are key, challenge your creativity as every option is welcome! Bob is just a normal Human with no superpowers, but given the right tools he can do a lot. Misuse the tools and he can do even more! The world of Human: Fall Flat features advanced physics and innovative controls that cater for a wide range of challenges. Bob’s dreams of falling are riddled with puzzles to solve and distractions to experiment with for hilarious results. The worlds may be fantastical, but the laws of physics are very real. FEATURES: ONLINE MULTIPLAYER UP TO 8 PLAYERS Fall into or host private/public lobbies with your friends and watch Bob fall, flail, wobble and stumble together. Need a hand getting that boulder on to a catapult, or need someone to break that wall? The more Bob the more Mayhem! THE WOBBLY ART OF PARKOUR Direct and complete control of Bob. Nothing is scripted and no limits imposed. Bob can walk (kinda straight), jump, grab anything, climb anything, carry anything. LOCAL CO-OP Play with a friend or a relative in split-screen, work together to achieve any task or spend an hour throwing each other about in the craziest ways possible. CUSTOMISATION Paint your own custom Bob, dress him with silly suits or even import your face onto his via webcam. SURREAL LANDSCAPES Explore open-ended levels which obey the rules of physics. Interact with almost every available object in the game and go almost everywhere - like playground of freedom. UNLIMITED REPLAY VALUE Fall into Bob's dreams as many time as you want, try new paths and discover all secrets. Think outside of the box is the philosophy! Fall into Bob's dreams now and see how good your catapulting skills are! ",,"Minimum:,OS:,Windows XP/Vista/7/8/8.1/10 x86 and x64,Processor:,Intel Core2 Duo E6750 (2 * 2660) or equivalent | AMD Athlon 64 X2 Dual Core 6000+ (2 * 3000) or equivalent,Memory:,1024 MB RAM,Graphics:,GeForce GT 740 (2048 MB) or equivalent | Radeon HD 5770 (1024 MB),Storage:,500 MB available space,Minimum:,OS:,OS X 10.9 and higher,Processor:,Intel Core2 Duo E6750 (2 * 2660) or equivalent | AMD Athlon 64 X2 Dual Core 6000+ (2 * 3000) or equivalent,Memory:,1024 MB RAM,Graphics:,GeForce GT 740 (2048 MB) or equivalent | Radeon HD 5770 (1024 MB),Storage:,500 MB available space,Additional Notes:,Requires a two-button mouse or controller","Recommended:,OS:,Windows XP/Vista/7/8/8.1/10 x86 and x64,Processor:,Intel Core2 Quad Q9300 (4 * 2500) or equivalent | AMD A10-5800K APU (4*3800) or equivalent,Memory:,2048 MB RAM,Graphics:,GeForce GTX 460 (1024 MB) or equivalent | Radeon HD 7770 (1024 MB),Storage:,500 MB available space,Recommended:,OS:,OS X 10.9 and higher,Processor:,Intel Core2 Quad Q9300 (4 * 2500) or equivalent | AMD A10-5800K APU (4*3800) or equivalent,Memory:,2048 MB RAM,Graphics:,GeForce GTX 460 (1024 MB) or equivalent | Radeon HD 7770 (1024 MB),Storage:,500 MB available space,Additional Notes:,Requires a two-button mouse or controller",$14.99,$17.58 +https://store.steampowered.com/app/644930/They_Are_Billions/,app,They Are Billions,They Are Billions is a Steampunk strategy game set on a post-apocalyptic planet. Build and defend colonies to survive against the billions of the infected that seek to annihilate the few remaining living humans. Can humanity survive after the zombie apocalypse?,"Very Positive,(192),- 83% of the 192 user reviews in the last 30 days are positive.","Very Positive,(12,127),- 85% of the 12,127 user reviews for this game are positive.","Dec 12, 2017",Numantian Games,"Numantian Games,Numantian Games","Early Access,Base Building,Strategy,Zombies,Survival,RTS,Steampunk,City Builder,Tower Defense,Post-apocalyptic,Building,Singleplayer,Resource Management,Real-Time with Pause,Early Access,Difficult,Tactical,Management,Indie,Isometric","Single-player,Steam Achievements,Steam Trading Cards,Steam Cloud,Stats,Steam Leaderboards","English,Spanish - Spain,French,German,Japanese,Korean,Polish,Portuguese - Brazil,Russian,Simplified Chinese,Traditional Chinese,Italian",34,"Strategy,Early Access"," About This Game They Are Billions is a strategy game in a distant future about building and managing human colonies after a zombie apocalypse destroyed almost all of human kind. Now there are only a few thousand humans left alive that must struggle to survive under the threat of the infection. Billions of infected roam around the world in massive swarms seeking the last living human colonies. Survival Mode - Available Now! In this mode, a random world is generated with its own events, weather, geography, and infected population. You must build a successful colony that must survive for a specific period of time against the swarms of infected. It is a fast and ultra addictive game mode. We plan to release a challenge of the week where all players must play the same random map. The best scores will be published in a leaderboard. Real Time with Pause This is a real-time strategy game, but don’t get too nervous. You can pause the action to take the best strategic and tactical decisions. In Pause Mode, you can place structures to build, give orders to your army, or consult all of the game’s information. This game is all about strategy, not player performance or the player’s skill to memorize and quickly execute dozens of key commands. Pause the game and take all the time you need! Build your Colony Build dwellings and acquire food for the colonists. They will come to live and work for the colony. Collect resources from the environment using various building structures. Upgrade buildings to make them more efficient. Expand the energy distribution of the colony by placing Tesla Towers and build mills and power plants to feed the energy to your buildings. Build walls, gates, towers, and structures to watch the surroundings. Don’t let the infected take over the colony! Build an Army What kind of people would want to combat the infected? Only the maddest ones. Train and contract mercenaries to protect the colony. They demand their money and food, and you will have to listen to their awful comments, but these tormented heroes will be your best weapon to destroy the infected. Every unit is unique and has their own skills and personality – discover them! Thousands of Units on Screen Yes! They are billions! The world is full of infected creatures… they roam, smell, and listen. Every one of them has their own AI. Make noise and they will come – kill some of them to access an oil deposit and hundred of them will come to investigate. We have created our custom engine to handle hordes of thousands of the infected, up to 20,000 units in real time. Do you think your colony is safe? Wait for the swarms of thousands of infected that are roaming the world. Sometimes your colony is in their path! Prevent the Infection If just one of the infected breaks into a building, all the colonies and workers inside will become infected. The infected workers will then run rabid to infect more buildings. Infections must be eradicated from the beginning, otherwise, it will grow exponentially becoming impossible to contain. Beautiful 4K Graphics! Prepare to enjoy these ultra high definition graphics. Our artists have created tons of art pieces: Beautiful buildings with their own animations, thousands of frames of animation to get the smoothest movements and everything with a crazy Steampunk and Victorian style! Future Game Mode - Campaign In the campaign mode, you have to help the surviving humans to reconquer their country. There are dozens of missions with different goals: building colonies, tactical missions, rescue missions... You can decide the path of progress and mission - research new technologies, advances and upgrades to improve your colonies and army. We are working right now on the campaign and we expect it will be available for Winter 2018. The campaign will be included for free to all the buyers of the Steam Early Access version.",,"Minimum:,OS:,Windows 7, 8, 10 (32 and 64 bits),Processor:,INTEL, AMD 2 cores CPU at 2Ghz,Memory:,4 GB RAM,Graphics:,Intel HD3000, Radeon, Nvidia card with shader model 3, 1GB video ram.,DirectX:,Version 9.0c,Storage:,4 GB available space,Additional Notes:,Minimum resolution: 1360x768, recomended FULL HD 1920x1080.","Recommended:,OS:,Windows 7, 8, 10 (64 bits),Processor:,INTEL. AMD 4 cores CPU at 3Ghz,Memory:,8 GB RAM,Graphics:,Radeon 7950 or above, Nvidia GTX 670 or above. 4GB video ram.,DirectX:,Version 9.0c,Storage:,4 GB available space,Additional Notes:,4K Monitor (3840x2160)",$29.99, +https://store.steampowered.com/app/774241/Warhammer_Chaosbane/,app,Warhammer: Chaosbane,"In a world ravaged by war and dominated by magic, you must rise up to face the Chaos hordes. Playing solo or with up to four players in local or online co-op, choose a hero from four character classes and prepare for epic battles wielding some of the most powerful artefacts of the Old World.",,"Mixed,(904),- 44% of the 904 user reviews for this game are positive.","May 31, 2019",Eko Software,"Bigben Interactive,Bigben Interactive","RPG,Adventure,Hack and Slash,Action,Action RPG,Games Workshop,Violent,Fantasy,Co-op,War,Isometric,Dark Fantasy,Warhammer 40K,Historical,Colorful","Single-player,Multi-player,Co-op,Online Co-op,Local Co-op,Steam Achievements,Full controller support,Steam Trading Cards,Steam Cloud","English,French,Italian,German,Spanish - Spain,Simplified Chinese,Traditional Chinese,Korean,Spanish - Latin America,Japanese,Polish,Portuguese - Brazil,Russian",43,"Action,Adventure,RPG"," About This Game “Keep your eyes on this one, because it’s one quality Action RPG” – Entertainment Buddha In a world ravaged by war and dominated by magic, you are the last hope for the Empire of Man against the Chaos hordes. Playing solo or with up to 4 in local or online co-op, choose a hero from 4 character classes with unique and complementary skills, and prepare for epic battles wielding some of the most powerful artefacts of the Old World. • THE FIRST HACK AND SLASH set in the Warhammer Fantasy world, told through an all-new story written by Mike Lee (a Black Library author) and featuring a soundtrack composed by Chance Thomas. • FEROCIOUS BATTLES: from the sewers of Nuln to the ruined streets of Praag, fight your way through monster hordes using over 180 different powers. Activate your bloodlust, a devastating skill, to escape the most perilous situations. • 4 CHARACTER CLASSES , each with unique skills and customisation: a soldier of the Empire who can take heavy damage, a Dwarf specialising in melee combat, a High Elf who deals ranged damage by manipulating magic or a Wood Elf who lays deadly traps and wields the bow like no other! • AN XXL BESTIARY with over 70 monsters aligned with the Chaos Gods and unique bosses. Battle Nurgle's minions, Khorne's spawn and waves of other vile creatures! • OPTIMIZED FOR CO-OP : solo or with up to 4 players, local or online, the class synergy and interface have been designed for co-op. Combine different skills and powers to create even more devastating effects. • HIGH REPLAY VALUE : Story mode, a boss rush mode, countless dungeons and regular updates offer a rich and varied gaming experience. And with 10 difficulty levels, you can find the right challenge to test your abilities. "," Mature Content Description The developers describe the content like this: This game may contain content not appropriate for all ages, or may not be appropriate for viewing at work: Frequent Violence ","Minimum:,Requires a 64-bit processor and operating system,OS:,64bits version of Windows® 7, Windows® 8, Windows® 10,Processor:,Intel® Core i3 or AMD Phenom™ II X3,Memory:,6 GB RAM,Graphics:,NVIDIA® GeForce® GTX 660 or AMD Radeon™ HD 7850 with 2 GB RAM,DirectX:,Version 11,Storage:,20 GB available space,Sound Card:,DirectX Compatible Soundcard","Recommended:,Requires a 64-bit processor and operating system,OS:,64bits version of Windows® 7, Windows® 8, Windows® 10,Processor:,Intel® Core i5 or AMD FX 8150,Memory:,6 GB RAM,Graphics:,NVIDIA® GeForce® GTX 780 or AMD Radeon™ R9 290 with 2 GB RAM,DirectX:,Version 11,Storage:,20 GB available space,Sound Card:,DirectX Compatible Soundcard",$49.99, +https://store.steampowered.com/app/527230/For_The_King/,app,For The King,"For The King is a strategic RPG that blends tabletop and roguelike elements in a challenging adventure that spans the realms. Set off on a single player experience or play cooperatively both online and locally. INTO THE DEEP ADVENTURE, NOW AVAILABLE FOR FREE!","Very Positive,(67),- 80% of the 67 user reviews in the last 30 days are positive.","Very Positive,(4,600),- 83% of the 4,600 user reviews for this game are positive.","Apr 19, 2018",IronOak Games,"Curve Digital,Curve Digital","RPG,Turn-Based Combat,Adventure,Online Co-Op,Co-op,Strategy,Rogue-like,Turn-Based,Multiplayer,Turn-Based Strategy,Party-Based RPG,Indie,Fantasy,Board Game,Strategy RPG,Hex Grid,Rogue-lite,Difficult,Local Co-Op,Early Access","Single-player,Multi-player,Online Multi-Player,Local Multi-Player,Co-op,Online Co-op,Local Co-op,Shared/Split Screen,Steam Achievements,Full controller support,Steam Trading Cards,Steam Cloud","English,French,Italian,German,Spanish - Spain,Portuguese - Brazil,Russian,Simplified Chinese,Traditional Chinese,Polish,Japanese,Korean",72,"Adventure,Indie,RPG,Strategy"," About This Game Into The Deep, a brand new adventure sets sail for free, NOW! Includes: Dungeon Crawl Adventure Frost Adventure Adventure Gold Rush Un-cooperative Mode Hildebrants Cellar Adventure Into The Deep Adventure The King is dead, murdered by an unknown assailant. Now the once peaceful kingdom of Fahrul is in chaos. With nowhere left to turn and stretched beyond her means, the queen has put out a desperate plea to the citizens of the land to rise up and help stem the tide of impending doom. Set off with your make-shift party, either single player , local , or online co-op . Choose to split your party up and cover more ground, or stick together for protection. A sound strategy can mean the difference between life and death. For The King is a challenging blend of Strategy , JRPG Combat , and Roguelike elements. Each play through is made unique with procedural maps , quests, and events. Brave the relentless elements, fight the wicked creatures, sail the seas and delve into the dark underworld. None before you have returned from their journey. Will you be the one to put an end to the Chaos? Fight and die as a party in fast paced and brutal turn-based combat using a unique slot system for attacks and special abilities. Find and gather herbs for your trusty pipe to heal your wounds and cure your maladies. Set up safe camps or brave the horrors that nightfall brings. Just remember adventurer, you do this not for the riches or fame but for your village, for your realm, For The King! ",,"Minimum:,Requires a 64-bit processor and operating system,OS:,Windows 7 / 8 / 8.1 / 10 x64,Processor:,Intel Core2 Duo E4300 (2 * 1800) / AMD Athlon Dual Core 4450e (2 * 2300) or equivalent,Memory:,4096 MB RAM,Graphics:,GeForce 8800 GTX (768 MB) / Intel HD 4600 / Radeon HD 3850 (512 MB),DirectX:,Version 9.0c,Storage:,3 GB available space,Minimum:,Requires a 64-bit processor and operating system,OS:,OSX 10.10.5 Yosemite or higher,Processor:,Intel Core i5-2520M (2 * 2500),Memory:,4096 MB RAM,Graphics:,GeForce GT 750M (1024 MB),Storage:,3 GB available space,Minimum:,Requires a 64-bit processor and operating system,OS:,Ubuntu 17.10 (x64) or Mint 18.3 (Cinnamon) (x64) or Ubuntu 16.04 (x64),Processor:,Intel Core2 Duo E4300 (2 * 1800) / AMD Athlon Dual Core 4450e (2 * 2300) or equivalent,Memory:,4096 MB RAM,Graphics:,GeForce 8800 GTX (768 MB) / Intel HD 4600 or equivalent,Storage:,3 GB available space","Recommended:,Requires a 64-bit processor and operating system,OS:,Windows 7 / 8 / 8.1 / 10 x64,Processor:,Intel Core i5-4570T (2* 2900) / AMD FX-6100 (6 * 3300) or equivalent,Memory:,4096 MB RAM,Graphics:,GeForce GTX 750 Ti (2048 MB) / Radeon HD 7850 (2048 MB),DirectX:,Version 9.0c,Storage:,3 GB available space,Recommended:,Requires a 64-bit processor and operating system,OS:,OSX 10.10.5 Yosemite or higher,Processor:,Intel Core i5-6500 (4 * 3200),Memory:,8192 MB RAM,Graphics:,AMD Radeon R9 M390 (2048 MB),Storage:,3 GB available space,Recommended:,Requires a 64-bit processor and operating system,OS:,Ubuntu 17.10 (x64) or Mint 18.3 (Cinnamon) (x64) or Ubuntu 16.04 (x64),Processor:,Intel Core i5-4570T (2* 2900) / AMD FX-6100 (6 * 3300) or equivalent,Memory:,4096 MB RAM,Graphics:,GeForce GTX 750 Ti (2048 MB) / Radeon HD 7850 (2048 MB),Storage:,3 GB available space",$19.99, +https://store.steampowered.com/app/567640/Danganronpa_V3_Killing_Harmony/,app,Danganronpa V3: Killing Harmony,"A new cast of 16 characters find themselves kidnapped and imprisoned in a school. Inside, some will kill, some will die, and some will be punished. Reimagine what you thought high-stakes, fast-paced investigation was as you investigate twisted murder cases and condemn your new friends to death.","Very Positive,(78),- 82% of the 78 user reviews in the last 30 days are positive.","Very Positive,(3,547),- 84% of the 3,547 user reviews for this game are positive.","Sep 25, 2017","Spike Chunsoft Co., Ltd.","Spike Chunsoft Co., Ltd.,Spike Chunsoft Co., Ltd.","Story Rich,Anime,Visual Novel,Detective,Mystery,Great Soundtrack,Female Protagonist,Singleplayer,Adventure,Psychological Horror,Dark Humor,Horror,Dark Comedy,Puzzle,Atmospheric,Memes,Dark,Funny,Comedy,Dating Sim","Single-player,Steam Achievements,Full controller support,Steam Trading Cards,Steam Cloud","English,French,Japanese,Simplified Chinese,Traditional Chinese",41,Adventure," About This Game Welcome to a new world of Danganronpa, and prepare yourself for the biggest, most exhilarating episode yet. Set in a “psycho-cool” environment, a new cast of 16 characters find themselves kidnapped and imprisoned in a school. Inside, some will kill, some will die, and some will be punished. Reimagine what you thought high-stakes, fast-paced investigation was as you investigate twisted murder cases and condemn your new friends to death. Key Features A New Danganronpa Begins: Forget what you thought you knew about Danganronpa and join a completely new cast of Ultimates for a brand-new beginning. Murder Mysteries: In a world where everyone is trying to survive, nobody’s motivations are quite what they seem. Use your skills to solve each new murder or meet a gruesome end. Lie, Panic, Debate! The world is shaped by our perception of it. Fast-paced trial scenes will require lies, quick wits, and logic to guide your classmates to the right conclusions. New Minigames: Between the madness of murdered peers and deadly trials, enjoy an abundance of brand-new minigames! ",,"Minimum:,Requires a 64-bit processor and operating system,OS:,Windows 7 64-bit,Processor:,Intel Core i3-4170 @ 3.70GHz,Memory:,4 GB RAM,Graphics:,NVIDIA@ GeForce@ GTX 460 or better,DirectX:,Version 11,Storage:,26 GB available space,Sound Card:,DirectX compatible soundcard or onboard chipset","Recommended:,Requires a 64-bit processor and operating system,OS:,Windows 7 64-bit,Processor:,Intel Core i5-4690K @3.50GHz,Memory:,8 GB RAM,Graphics:,NVIDIA@ GeForce@ GTX 960,DirectX:,Version 11,Storage:,26 GB available space,Sound Card:,DirectX compatible soundcard or onboard chipset",$39.99,$59.97 +https://store.steampowered.com/app/323370/TERA/,app,TERA,"From En Masse Entertainment, TERA is at the forefront of a new breed of MMO. With True Action Combat - aim, dodge, and time your attacks for intense and rewarding tactical combat. Add the deep social experience of a MMO to best-in-class action combat mechanics for a unique blend of both genres. Play now for free!","Mixed,(76),- 63% of the 76 user reviews in the last 30 days are positive.","Mostly Positive,(14,184),- 78% of the 14,184 user reviews for this game are positive.","May 5, 2015","Bluehole, Inc.","En Masse Entertainment,En Masse Entertainment","Free to Play,MMORPG,Massively Multiplayer,RPG,Open World,Action,Fantasy,Adventure,Anime,Third Person,Character Customization,Action RPG,Multiplayer,Co-op,PvP,Hack and Slash,PvE,Cute,Controller,Nudity","Multi-player,MMO,Co-op,Steam Trading Cards,Partial Controller Support",English,NaN,"Action,Adventure,Free to Play,Massively Multiplayer,RPG"," About This Game TERA is at the forefront of a new breed of MMO. With True Action Combat - aim, dodge, and time your attacks for intense and rewarding tactical combat. Add the deep social experience of a MMO to best-in-class action combat mechanics for a unique blend of both genres. Play now for free! Experience an action MMO that goes beyond ""point and click!"" In TERA , it's your skill, position, timing, and aim that determine the outcome of combat—not how quickly you cycle through targets with the Tab key. If you prefer to lie down on your couch while playing games (we do!), you'll be happy to hear that TERA also offers controller support. Team up with friends to take down Big Ass Monsters or put your combat skills to the test against other players in one of the many PvP battlegrounds TERA offers. Customize your choice of seven character races and nine classes with unique battle styles. Fight thousands of monsters throughout a variety of landscapes, and embark on thousands of quests in a game world rich in history and lore. In a truly free-to-play experience. TERA imposes no artificial cap on classes, zones, or what you can do, or how good you can be - all of the content in the game can be experienced without paying a single penny. To support the continued development of TERA , we offer account services and cosmetic items - costumes, accessories, weapon skins, mounts, and more. We heard players on Steam really like hats... Whether you fight as part of a guild or join an alliance, being part of TERA's vibrant community means that there is always someone to share your journey with."," Mature Content Description The developers describe the content like this: This Game may contain content not appropriate for all ages, or may not be appropriate for viewing at work: General Mature Content ","Minimum:,OS:,Windows 7, 32-bit,Processor:,Intel i3 2130 / AMD FX 4130,Memory:,4 GB RAM,Graphics:,GeForce 9800 GT / Radeon HD 3870,DirectX:,Version 9.0c,Network:,Broadband Internet connection,Storage:,45 GB available space","Recommended:,OS:,Windows 7, 8, 8.1, 10, 64-bit,Processor:,Intel i5 3570 / AMD FX 6350,Memory:,8 GB RAM,Graphics:,GeForce GTS 450 / Radeon HD 4890 1GB,DirectX:,Version 9.0c,Network:,Broadband Internet connection,Storage:,55 GB available space",Free to Play, +https://store.steampowered.com/app/393080/Call_of_Duty_Modern_Warfare_Remastered/,app,Call of Duty®: Modern Warfare® Remastered,"One of the most critically-acclaimed games in history, Call of Duty: Modern Warfare is back, remastered in true high-definition, featuring improved textures, physically based rendering, high-dynamic range lighting and much more.","Mixed,(33),- 51% of the 33 user reviews in the last 30 days are positive.","Mixed,(1,118),- 51% of the 1,118 user reviews for this game are positive.","Jul 27, 2017","Raven Software,Beenox","Activision,Activision","FPS,Action,Shooter,Multiplayer,Violent,War,Singleplayer,First-Person,Military,Remake,Controller,Casual,Classic","Single-player,Online Multi-Player,Online Co-op,Steam Achievements","English,French,Italian,German,Spanish - Spain,Japanese,Korean,Polish,Portuguese - Brazil,Russian,Simplified Chinese,Traditional Chinese",50,Action," About This Game One of the most critically-acclaimed games in history, Call of Duty: Modern Warfare is back, remastered in true high-definition, featuring improved textures, physically based rendering, high-dynamic range lighting and much more. Developed by Infinity Ward, the award-winning Call of Duty® 4: Modern Warfare® set a new standard upon its release for intense, cinematic action, while receiving universal praise as one of the most influential video games of all-time. Winner of numerous Game of the Year honors, Call of Duty 4: Modern Warfare became an instant classic and global phenomenon that set the bar for first-person shooters, and now it returns for a new generation of fans. Relive one of the most iconic campaigns in history, as you are transported around the globe, including fan favorite missions ""All Ghillied Up,"" ""Mile High Club,"" and ""Crew Expendable."" You’ll suit up as unforgettable characters Sgt. John ""Soap"" MacTavish, Capt. John Price and more, as you battle a rogue enemy group across global hotspots from Eastern Europe and rural Russia, all the way to the Middle East. Through an engaging narrative full of twists and turns, call on sophisticated technology and superior firepower as you coordinate land and air strikes on a battlefield where speed and accuracy are essential to victory. Additionally, team up with your friends in the online mode that redefined Call of Duty by introducing killstreaks, XP, Prestige and more in customizable, classic multiplayer modes.",,"Minimum:,Requires a 64-bit processor and operating system,OS:,Windows 7 64-Bit or later,Processor:,Intel Core i3-3225 @ 3.30GHz or equivalent,Memory:,8 GB RAM,Graphics:,NVIDIA GeForce GTX 660 2GB / AMD Radeon HD 7850 2GB,DirectX:,Version 11,Network:,Broadband Internet connection,Sound Card:,DirectX 11 Compatible,Additional Notes:,Disk space requirement may change over time.","Recommended:,Requires a 64-bit processor and operating system",1.020,$906.48 +https://store.steampowered.com/app/253250/Stonehearth/,app,Stonehearth,"Pioneer a living world full of warmth, heroism, and mystery. Help a small group of settlers build a home for themselves in a forgotten land. Establish a food supply, build shelter, defend your people, monitor their moods, and find a way to grow and expand, facing challenges at every step.","Mixed,(66),- 40% of the 66 user reviews in the last 30 days are positive.","Mostly Positive,(5,484),- 75% of the 5,484 user reviews for this game are positive.","Jul 25, 2018",Radiant Entertainment,"(none),(none)","City Builder,Building,Sandbox,Strategy,Survival,Simulation,Crafting,Voxel,Early Access,Indie,Singleplayer,Open World,RPG,Management,Multiplayer,Fantasy,Cute,Adventure,God Game,RTS","Single-player,Multi-player,Online Multi-Player,Local Multi-Player,Co-op,Online Co-op,Local Co-op,Steam Trading Cards,Steam Workshop",English,NaN,"Indie,Simulation,Strategy"," About This Game In Stonehearth, you pioneer a living world full of warmth, heroism, and mystery. Help a small group of settlers build a home for themselves in a forgotten land. You’ll need to establish a food supply, build shelter, defend your people, monitor their moods, and find a way to grow and expand, facing challenges at every step. Starting from procedurally generated terrain with dynamic AI encounters, Stonehearth combines community management and combat with infinite building possibilities. It’s designed to be moddable at every level, from your city to the people and creatures inhabiting the world, and will ship with the tools and documentation for you to add your own customizations to the game, and share them with friends. Build and Grow Your City The heart of the game is city building and management. When you're just starting out, you'll need to juggle tasks like obtaining a sustainable food supply, building shelter, and defending your fledgling settlement from raiders and other threats. Once you've achieved a foothold in the world, it's up to you to write the destiny for your people. You have the flexibility to choose your own path in this game. Do you want to build a great conquering empire? A vibrant trade city? A spiritual monastery? We really want you to feel like this is your settlement, and give you the tools that make it look and operate exactly as you wish. Level Up Your Settlers All the settlers in your towns have jobs. A job is like a class in a role playing game. Each job has a specific role like hauling materials, building, crafting, and fighting. As your hearthlings work at a job they will gain experience and levels. Some jobs, when they meet certain prerequisites, can upgrade to entirely new jobs with new capabilities. Usually, to assign someone a new job you'll also need to craft a tool for them. The Mason can craft blocks, statues, and tools from stone, but to do it he'll need a mallet and chisel crafted by the carpenter. Our goal is to have a job tree that's both very wide and very deep, so there will be plenty of different kinds of things to do in the game, but also a lot of depth to explore if you want to concentrate on any one area. Player Driven Legacy Through Modding We LOVE mods and want to make it as easy as possible to author and share mods. Want to see a new kind of sword in the game? You can model it, define its stats, and then craft it in game. You can also share the design with other players so they can enjoy it too, or bring their authored content into your game. As a modder you’ll be able to do basically anything that we as developers can do: introduce new items and monsters, write new scripted adventures, influence the AI, you name it. It goes back to that original pen and paper RPG experience, where “the game” is a collaboration between the core ruleset and the stories crafted by the gamemaster.",,"Minimum:,OS:,Windows 7/8/8.1/10 (32-bit or 64-bit),Processor:,Intel or AMD Dual-Core, 1.7 GHz+,Memory:,4 GB RAM,Graphics:,nVidia GeForce GT 430 512MB, Radeon HD 7570M, Intel HD 4000,Storage:,2 GB available space,Additional Notes:,OS Updates: Windows 7 SP1,Minimum:,OS:,macOS 10.11 (El Capitan) or later,Processor:,Intel Core i5 1.7GHz,Memory:,8 GB RAM,Graphics:,Radeon HD 5870 or GeForce 630, or Intel HD Graphics 4000,Storage:,2 GB available space","Recommended:,OS:,Windows 10,Processor:,Intel or AMD Quad-Core, 2.8 GHz+,Memory:,16 GB RAM,Graphics:,nVidia GeForce 780 or better, AMD Radeon RX 580 or better,Network:,Broadband Internet connection,Storage:,5 GB available space,Additional Notes:,Hard Drive: SSD for optimal performance,Recommended:,OS:,macOS 10.12 (Sierra) or later,Processor:,Intel Core i7 2.8 GHz+,Memory:,16 GB RAM,Graphics:,NVIDIA GeForce GT 750M or better,Storage:,5 GB available space",$19.99, +https://store.steampowered.com/bundle/5641/Hearts_of_Iron_IV_Mobilization_Pack/,bundle,Hearts of Iron IV: Mobilization Pack,Hearts of Iron IV: Mobilization Pack bundle,NaN,NaN,NaN,Paradox Development Studio,Paradox Interactive,NaN,"Single-player,Multi-player,Online Multi-Player,Co-op,Cross-Platform Multiplayer,Downloadable Content,Steam Achievements,Steam Trading Cards,Steam Workshop,Steam Cloud","English, French, German, Polish, Portuguese - Brazil, Russian, Spanish - Spain",NaN,"Simulation,Strategy",NaN,NaN,NaN,NaN,,$94.45 +https://store.steampowered.com/app/597170/Clone_Drone_in_the_Danger_Zone/,app,Clone Drone in the Danger Zone,"Clone Drone in the Danger Zone is a third person sword fighter where any part of your body can be sliced off. With your mind downloaded into a robot gladiator, you must survive the sinister trials of the arena.","Very Positive,(88),- 94% of the 88 user reviews in the last 30 days are positive.","Very Positive,(1,901),- 94% of the 1,901 user reviews for this game are positive.","Mar 16, 2017",Doborog Games,"Doborog Games,Doborog Games","Early Access,Robots,Action,Swordplay,Fighting,Early Access,Indie,Funny,Singleplayer,Voxel,Futuristic,Pixel Graphics,Multiplayer,Third Person,Comedy,Difficult,Survival,Dark Humor,Philisophical,Rogue-like","Single-player,Multi-player,Online Multi-Player,Steam Achievements,Full controller support,Captions available,Steam Workshop,Steam Cloud,Includes level editor",English,12,"Action,Indie,Early Access"," About This Game CONGRATULATIONS HUMAN! 🤖 Apologies for the brief agony while we harvested your mind. But now your thought patterns are safely encased in this sleek, shiny robot! With a laser sword. SURVIVE AND PERHAPS YOU WILL EARN UPGRADES. GOOD LUCK IN THE ARENA. We hope you survive longer than the last contestant... Oh, and the one before that... Game Modes 1. Story Mode - One part epic tale of human defiance, another part laser swords. 2. Endless Mode - Challenge yourself to fight through 86 level variants spread across 5 difficulty tiers. Can you make it to 🏆TITANIUM🏆 ? 3. Twitch Mode - TWITCH PLAYS YOU . Your Twitch stream viewers earn coins, bet on your gameplay and spawn enemies to kill you (or give you ❤️s—if they really like you). !spawn jetpack2 Kappa Kappa Kappa 4. Challenge Mode - Bow-only, hammer-only, can you still survive the arena? 5. Level Editor - Make custom levels and challenges and share them with other humans! Explore the rich Workshop library of awesome human-built levels! 6. Online Multiplayer Duels (1v1) - Head to head combat! 7. Last Bot Standing​ (2-15 players online) - a Battle-Royale-like game mode with short, 5-10 minute play sessions. Survive, and shower in the remains of your fellow robot competitors. Features (in the game right now!) Epic Voxel Dismemberment : In addition to looking cool, the ability to cut off body parts is central to the gameplay. Jumping on one leg is a common occurrence. Large enemies need to be cut down to size before a fatal blow can be delivered. Sharp Sword Combat : Combat is fast and intense, putting great emphasis on movement, timing and positioning. You are never safe, as any blow can instantly kill or dismember. Entertaining Commentary : With 7,421 spoken words, Commentatron and Analysis-Bot provide a running commentary of your performance and react to your activities. Upgrade your robot: jetpack, bow, kicking, deflection, clones, giant hammers, FIRE 🔥🔥🔥. Terrifying robotic enemies that actually get pretty hard! Sword robots, bow robots, Spidertron 5000, Spidertron 6000, gigantic hammer bots. So many things to dismember. Alpha Limitations: See Early Access description above. Hone your sword skills now, before the rest of humanity gets its turn. JOIN THE HUMAN RESISTANCE! Or don't! It's your future-robo-funeral... 😈",,,,$14.99, +https://store.steampowered.com/app/899440/GOD_EATER_3/,app,GOD EATER 3,"Set in a post-apocalyptic setting, it’s up to your special team of God Eaters to take down god-like monsters devastating the world. With an epic story, unique characters, and all new God Arcs and Aragami, the latest evolution in ACTION is here!","Mostly Positive,(56),- 71% of the 56 user reviews in the last 30 days are positive.","Mostly Positive,(1,945),- 77% of the 1,945 user reviews for this game are positive.","Feb 7, 2019",BANDAI NAMCO Studios Inc.,"BANDAI NAMCO Entertainment,BANDAI NAMCO Entertainment","Anime,Action,Character Customization,Co-op,Hunting,Hack and Slash,JRPG,Multiplayer,RPG,Singleplayer,Great Soundtrack,Post-apocalyptic,Online Co-Op,Third Person,Story Rich,Action RPG,Adventure,Female Protagonist","Single-player,Online Multi-Player,Online Co-op,Steam Achievements,Steam Trading Cards,Partial Controller Support,Steam Cloud","English,Japanese,French,Italian,German,Spanish - Spain,Korean,Portuguese - Brazil,Russian,Spanish - Latin America,Traditional Chinese",32,Action," About This Game Rise Above a World of Desolation The latest entry in the hugely popular God Eater action series is here! • Fight in Style with Brand-new God Arcs! Expand your close-combat armory with the dual-wield God Arc “Biting Edge” and the two-handed moon axe """"Heavy Moon"""", or fight from afar with the new """"Ray Gun"""" God Arc! • New Abilities for Exhilarating Battles! Ground, Air, and Step attacks evolve into powerful techniques with Burst Arts, and the new Dive dash attack allows you full freedom of movement to hunt down wandering Aragami! • Fearsome New Threats: Ash Aragami and Devour Attacks! Dangerous new foes, Ash Aragami can utilize Devours Attacks and enter Burst Mode, increasing their strength exponentially! These enemies are not to be trifled with and will require you to take your weapon and your game to the next level!",,"Minimum:,Requires a 64-bit processor and operating system,OS:,Windows 7 64-bit, SP1,Processor:,Intel Core i5-3470 or AMD FX-8120,Memory:,4 GB RAM,Graphics:,GeForce GTX 760 or Radeon R9 290X,DirectX:,Version 11,Network:,Broadband Internet connection,Storage:,25 GB available space","Recommended:,Requires a 64-bit processor and operating system,OS:,Windows 10 64-bit,Processor:,Intel Core i7-3770 or AMD Ryzen 5 1600,Memory:,8 GB RAM,Graphics:,GeForce GTX 970 or Radeon R9 fury,DirectX:,Version 11,Network:,Broadband Internet connection,Storage:,25 GB available space",$59.99, +https://store.steampowered.com/app/767560/War_Robots/,app,War Robots,"War Robots is an online third-person 6v6 PvP shooter—we’re talking dozens of combat robots, hundreds of weapons combinations, and heated clan battles.","Mixed,(91),- 49% of the 91 user reviews in the last 30 days are positive.","Mixed,(1,797),- 44% of the 1,797 user reviews for this game are positive.","Apr 5, 2018",Pixonic,"Pixonic,Pixonic","Free to Play,Robots,Action,Multiplayer,FPS,Mechs,Massively Multiplayer,Shooter,PvP,Controller,Third-Person Shooter,Co-op,War","Multi-player,Online Multi-Player,Steam Trading Cards,In-App Purchases","English,French,Italian,German,Spanish - Spain,Dutch,Japanese,Korean,Polish,Portuguese - Brazil,Russian,Simplified Chinese,Thai,Traditional Chinese,Turkish",NaN,"Action,Free to Play"," About This Game War is raging, pilot! Are you ready for surprise attacks, intricate tactical maneuvers and the many sneaky tricks your rivals have in store for you? Destroy enemy robots, capture all the beacons, and upgrade your weapons to increase your battle robot’s combat strength, speed and durability. Prove yourself in each map and use different strategies and tactics to emerge victorious in battle. The renowned iOS and Android hit is coming to Steam! Fight other Pilots from all over the world and join millions of existing players! FEATURES: Tactical 6v6 PVP 45 battle robots with different strengths over 50 weapon types, including ballistic missiles, energy and plasma guns. What will you choose? 12 Maps to battle on! numerous possible combinations of robots and weapons. Create a war machine to fit your own playing style; create your own clan and lead it to glorious victories; join epic PvP battles against rivals from all over the world; complete military tasks for bonuses and earn the title Best Pilot. Onward, soldier! Victory is yours!",,"Minimum:,OS:,Windows 7,Processor:,2.5 GHz,Memory:,2 GB RAM,Graphics:,Intel HD Graphics 4000,DirectX:,Version 10,Storage:,1500 MB available space","Recommended:,OS:,Windows 10,Processor:,3.2 GHz,Memory:,8 GB RAM,Graphics:,NVIDIA GTX 960,DirectX:,Version 11,Network:,Broadband Internet connection,Storage:,2 GB available space",Free To Play, diff --git a/datajunction-query/docker/cockroachdb/steam-hours-played.csv b/datajunction-query/docker/cockroachdb/steam-hours-played.csv new file mode 100644 index 000000000..ea3a9ff01 --- /dev/null +++ b/datajunction-query/docker/cockroachdb/steam-hours-played.csv @@ -0,0 +1,50 @@ +151603712,"DOOM",purchase,1.0,0 +151603712,"DOOM",play,273.0,0 +151603712,"PLAYERUNKNOWN'S BATTLEGROUNDS",purchase,1.0,0 +151603712,"Grand Theft Auto V: Premium Online Edition",play,87.0,0 +151603712,"Devil May Cry 5",purchase,1.0,0 +151603712,"Devil May Cry 5",play,14.9,0 +151603712,"TERA",purchase,1.0,0 +151603712,"Call of Duty®: Modern Warfare® Remastered",play,12.1,0 +151603712,"Call of Duty®: Modern Warfare® Remastered",purchase,1.0,0 +151603712,"Clone Drone in the Danger Zone",play,8.9,0 +151603712,"Hearts of Iron IV: Mobilization Pack",purchase,1.0,0 +151603712,"DayZ",play,8.5,0 +151603712,"DayZ",purchase,1.0,0 +151603712,"Path of Exile",play,8.1,0 +151603712,"Warhammer: Chaosbane",purchase,1.0,0 +151603712,"Warhammer: Chaosbane",play,7.5,0 +151603712,"For The King",purchase,1.0,0 +151603712,"For The King",play,3.3,0 +151603712,"Danganronpa V3: Killing Harmony",purchase,1.0,0 +151603712,"Danganronpa V3: Killing Harmony",play,2.8,0 +151603712,"Human: Fall Flat",purchase,1.0,0 +151603712,"Human: Fall Flat",play,2.5,0 +151603712,"The Banner Saga",purchase,1.0,0 +151603712,"The Banner Saga",play,2.0,0 +151603712,"Call of Duty®: Modern Warfare® Remastered",purchase,1.0,0 +151603712,"Call of Duty®: Modern Warfare® Remastered",play,1.4,0 +151603712,"BioShock Infinite",purchase,1.0,0 +151603712,"BioShock Infinite",play,1.3,0 +151603712,"Call of Duty®: Modern Warfare® Remastered",purchase,1.0,0 +151603712,"Call of Duty®: Modern Warfare® Remastered",play,1.3,0 +151603712,"Call of Duty®: Modern Warfare® Remastered",purchase,1.0,0 +151603712,"Call of Duty®: Modern Warfare® Remastered",play,0.8,0 +151603712,"Grand Theft Auto IV",purchase,1.0,0 +151603712,"Grand Theft Auto IV",play,0.8,0 +151603712,"Grand Theft Auto IV",purchase,1.0,0 +151603712,"Grand Theft Auto IV",play,0.6,0 +151603712,"Grand Theft Auto V: Premium Online Edition",purchase,1.0,0 +151603712,"Grand Theft Auto V: Premium Online Edition",play,0.5,0 +151603712,"Grand Theft Auto V: Premium Online Edition",purchase,1.0,0 +151603712,"Grand Theft Auto V: Premium Online Edition",play,0.5,0 +151603712,"EVE Online",purchase,1.0,0 +151603712,"EVE Online",play,0.5,0 +151603712,"EVE Online",purchase,1.0,0 +151603712,"EVE Online",play,0.5,0 +151603712,"Stonehearth",purchase,1.0,0 +151603712,"Stonehearth",play,0.5,0 +151603712,"Hearts of Iron IV: Mobilization Pack",purchase,1.0,0 +151603712,"Hearts of Iron IV: Mobilization Pack",play,0.4,0 +151603712,"Hearts of Iron IV: Mobilization Pack",purchase,1.0,0 +151603712,"Hearts of Iron IV: Mobilization Pack",play,0.1,0 diff --git a/datajunction-query/docker/default.duckdb b/datajunction-query/docker/default.duckdb new file mode 100644 index 000000000..567046554 Binary files /dev/null and b/datajunction-query/docker/default.duckdb differ diff --git a/examples/docker/environment b/datajunction-query/docker/druid_environment similarity index 91% rename from examples/docker/environment rename to datajunction-query/docker/druid_environment index 20eceb0b8..c8f7753b3 100644 --- a/examples/docker/environment +++ b/datajunction-query/docker/druid_environment @@ -9,11 +9,11 @@ druid_emitter_logging_logLevel=debug druid_extensions_loadList=["druid-histogram", "druid-datasketches", "druid-lookups-cached-global", "postgresql-metadata-storage"] -druid_zk_service_host=zookeeper +druid_zk_service_host=druid_zookeeper druid_metadata_storage_host= druid_metadata_storage_type=postgresql -druid_metadata_storage_connector_connectURI=jdbc:postgresql://postgres:5432/druid +druid_metadata_storage_connector_connectURI=jdbc:postgresql://druid_postgres:5432/druid druid_metadata_storage_connector_user=druid druid_metadata_storage_connector_password=FoolishPassword diff --git a/examples/docker/druid_init.sh b/datajunction-query/docker/druid_init.sh similarity index 100% rename from examples/docker/druid_init.sh rename to datajunction-query/docker/druid_init.sh diff --git a/examples/docker/druid_spec.json b/datajunction-query/docker/druid_spec.json similarity index 88% rename from examples/docker/druid_spec.json rename to datajunction-query/docker/druid_spec.json index 634481ad4..8cc710c0b 100644 --- a/examples/docker/druid_spec.json +++ b/datajunction-query/docker/druid_spec.json @@ -32,6 +32,8 @@ }, "dimensionsSpec": { "dimensions": [ + "id", + "user_id", "text" ] }, @@ -39,16 +41,6 @@ { "name": "count", "type": "count" - }, - { - "name": "sum_id", - "type": "longSum", - "fieldName": "id" - }, - { - "name": "sum_user_id", - "type": "longSum", - "fieldName": "user_id" } ] } diff --git a/datajunction-query/docker/duckdb.sql b/datajunction-query/docker/duckdb.sql new file mode 100644 index 000000000..fde74312f --- /dev/null +++ b/datajunction-query/docker/duckdb.sql @@ -0,0 +1,552 @@ +CREATE SCHEMA roads; + +CREATE TABLE roads.repair_type ( + repair_type_id int, + repair_type_name string, + contractor_id string +); +INSERT INTO roads.repair_type VALUES +(1, 'Asphalt Overlay', 'Asphalt overlays restore roads to a smooth condition. This resurfacing uses the deteriorating asphalt as a base for which the new layer is added on top of, instead of tearing up the worsening one.'), +(2, 'Patching', 'Patching is the process of filling potholes or excavated areas in the asphalt pavement. Quick repair of potholes or other pavement disintegration helps control further deterioration and expensive repair of the pavement. Without timely patching, water can enter the sub-grade and cause larger and more serious pavement failures.'), +(3, 'Reshaping', 'This is necessary when a road surface it too damaged to be smoothed. Using a grader blade and scarifying if necessary, you rework the gravel sub-base to eliminate large potholes and rebuild a flattened crown.'), +(4, 'Slab Replacement', 'This refers to replacing sections of paved roads. It is a good option for when slabs are chipped, cracked, or uneven, and mitigates the need to replace the entire road when just a small section is damaged.'), +(5, 'Smoothing', 'This is when you lightly rework the gravel of a road without digging in too far to the sub-base. Typically, a motor grader is used in this operation with an attached blade. Smoothing is done when the road has minor damage or is just worn down a bit from use.'), +(6, 'Reconstruction', 'When roads have deteriorated to a point that it is no longer cost-effective to maintain, the entire street or road needs to be rebuilt. Typically, this work is done in phases to limit traffic restrictions. As part of reconstruction, the street may be realigned to improve safety or operations, grading may be changed to improve storm water flow, underground utilities may be added, upgraded or relocated, traffic signals and street lights may be relocated, and street trees and pedestrian ramps may be added.'); + +CREATE TABLE roads.municipality ( + municipality_id string, + contact_name string, + contact_title string, + local_region string, + state_id int +); +INSERT INTO roads.municipality VALUES +('New York', 'Alexander Wilkinson', 'Assistant City Clerk', 'Manhattan', 33), +('Los Angeles', 'Hugh Moser', 'Administrative Assistant', 'Santa Monica',5 ), +('Chicago', 'Phillip Bradshaw', 'Director of Community Engagement', 'West Ridge', 14), +('Houston', 'Leo Ackerman', 'Municipal Roads Specialist', 'The Woodlands', 44), +('Phoenix', 'Jessie Paul', 'Director of Finance and Administration', 'Old Town Scottsdale', 3), +('Philadelphia', 'Willie Chaney', 'Municipal Manager', 'Center City', 39), +('San Antonio', 'Chester Lyon', 'Treasurer', 'Alamo Heights', 44), +('San Diego', 'Ralph Helms', 'Senior Electrical Project Manager', 'Del Mar', 5), +('Dallas', 'Virgil Craft', 'Assistant Assessor (Town/Municipality)', 'Deep Ellum', 44), +('San Jose', 'Charles Carney', 'Municipal Accounting Manager', 'Santana Row', 5); + +CREATE TABLE roads.hard_hats ( + hard_hat_id int, + last_name string, + first_name string, + title string, + birth_date date, + hire_date date, + address string, + city string, + state string, + postal_code string, + country string, + manager int, + contractor_id int +); +INSERT INTO roads.hard_hats VALUES +(1, 'Brian', 'Perkins', 'Construction Laborer', cast('1978-11-28' as date), cast('2009-02-06' as date), '4 Jennings Ave.', 'Jersey City', 'NJ', '37421', 'USA', 9, 1), +(2, 'Nicholas', 'Massey', 'Carpenter', cast('1993-02-19' as date), cast('2003-04-14' as date), '9373 Southampton Street', 'Middletown', 'CT', '27292', 'USA', 9, 1), +(3, 'Cathy', 'Best', 'Framer', cast('1994-08-30' as date), cast('1990-07-02' as date), '4 Hillside Street', 'Billerica', 'MA', '13440', 'USA', 9, 2), +(4, 'Melanie', 'Stafford', 'Construction Manager', cast('1966-03-19' as date), cast('2003-02-02' as date), '77 Studebaker Lane', 'Southampton', 'PA', '71730', 'USA', 9, 2), +(5, 'Donna', 'Riley', 'Pre-construction Manager', cast('1983-03-14' as date), cast('2012-01-13' as date), '82 Taylor Drive', 'Southgate', 'MI', '33125', 'USA', 9, 4), +(6, 'Alfred', 'Clarke', 'Construction Superintendent', cast('1979-01-12' as date), cast('2013-10-17' as date), '7729 Catherine Street', 'Powder Springs', 'GA', '42001', 'USA', 9, 2), +(7, 'William', 'Boone', 'Construction Laborer', cast('1970-02-28' as date), cast('2013-01-02' as date), '1 Border St.', 'Niagara Falls', 'NY', '14304', 'USA', 9, 4), +(8, 'Luka', 'Henderson', 'Construction Laborer', cast('1988-12-09' as date), cast('2013-03-05' as date), '794 S. Chapel Ave.', 'Phoenix', 'AZ', '85021', 'USA', 9, 1), +(9, 'Patrick', 'Ziegler', 'Construction Laborer', cast('1976-11-27' as date), cast('2020-11-15' as date), '321 Gainsway Circle', 'Muskogee', 'OK', '74403', 'USA', 9, 3); + +CREATE TABLE roads.hard_hat_state ( + hard_hat_id int, + state_id int +); +INSERT INTO roads.hard_hat_state VALUES +(1, 2), +(2, 32), +(3, 28), +(4, 12), +(5, 5), +(6, 3), +(7, 16), +(8, 32), +(9, 41); + +CREATE TABLE roads.repair_order_details ( + repair_order_id int, + repair_type_id int, + price real NOT NULL, + quantity int, + discount real NOT NULL +); +INSERT INTO roads.repair_order_details VALUES +(10001, 1, 63708, 1, 0.05), +(10002, 4, 67253, 1, 0.05), +(10003, 2, 66808, 1, 0.05), +(10004, 4, 18497, 1, 0.05), +(10005, 7, 76463, 1, 0.05), +(10006, 4, 87858, 1, 0.05), +(10007, 1, 63918, 1, 0.05), +(10008, 6, 21083, 1, 0.05), +(10009, 3, 74555, 1, 0.05), +(10010, 5, 27222, 1, 0.05), +(10011, 5, 73600, 1, 0.05), +(10012, 3, 54901, 1, 0.01), +(10013, 5, 51594, 1, 0.01), +(10014, 1, 65114, 1, 0.01), +(10015, 1, 48919, 1, 0.01), +(10016, 3, 70418, 1, 0.01), +(10017, 1, 29684, 1, 0.01), +(10018, 2, 62928, 1, 0.01), +(10019, 2, 97916, 1, 0.01), +(10020, 5, 44120, 1, 0.01), +(10021, 1, 53374, 1, 0.01), +(10022, 2, 87289, 1, 0.01), +(10023, 2, 92366, 1, 0.01), +(10024, 2, 47857, 1, 0.01), +(10025, 1, 68745, 1, 0.01); + +CREATE TABLE roads.repair_orders ( + repair_order_id int, + municipality_id string, + hard_hat_id int, + order_date date, + required_date date, + dispatched_date date, + dispatcher_id int +); +INSERT INTO roads.repair_orders VALUES +(10001, 'New York', 1, cast('2007-07-04' as date), cast('2009-07-18' as date), cast('2007-12-01' as date), 3), +(10002, 'New York', 3, cast('2007-07-05' as date), cast('2009-08-28' as date), cast('2007-12-01' as date), 1), +(10003, 'New York', 5, cast('2007-07-08' as date), cast('2009-08-12' as date), cast('2007-12-01' as date), 2), +(10004, 'Dallas', 1, cast('2007-07-08' as date), cast('2009-08-01' as date), cast('2007-12-01' as date), 1), +(10005, 'San Antonio', 8, cast('2007-07-09' as date), cast('2009-08-01' as date), cast('2007-12-01' as date), 2), +(10006, 'New York', 3, cast('2007-07-10' as date), cast('2009-08-01' as date), cast('2007-12-01' as date), 2), +(10007, 'Philadelphia', 4, cast('2007-04-21' as date), cast('2009-08-08' as date), cast('2007-12-01' as date), 2), +(10008, 'Philadelphia', 5, cast('2007-04-22' as date), cast('2009-08-09' as date), cast('2007-12-01' as date), 3), +(10009, 'Philadelphia', 3, cast('2007-04-25' as date), cast('2009-08-12' as date), cast('2007-12-01' as date), 2), +(10010, 'Philadelphia', 4, cast('2007-04-26' as date), cast('2009-08-13' as date), cast('2007-12-01' as date), 3), +(10011, 'Philadelphia', 4, cast('2007-04-27' as date), cast('2009-08-14' as date), cast('2007-12-01' as date), 1), +(10012, 'Philadelphia', 8, cast('2007-04-28' as date), cast('2009-08-15' as date), cast('2007-12-01' as date), 3), +(10013, 'Philadelphia', 4, cast('2007-04-29' as date), cast('2009-08-16' as date), cast('2007-12-01' as date), 1), +(10014, 'Philadelphia', 6, cast('2007-04-29' as date), cast('2009-08-16' as date), cast('2007-12-01' as date), 2), +(10015, 'Philadelphia', 2, cast('2007-04-12' as date), cast('2009-08-19' as date), cast('2007-12-01' as date), 3), +(10016, 'Philadelphia', 9, cast('2007-04-13' as date), cast('2009-08-20' as date), cast('2007-12-01' as date), 3), +(10017, 'Philadelphia', 2, cast('2007-04-14' as date), cast('2009-08-21' as date), cast('2007-12-01' as date), 3), +(10018, 'Philadelphia', 6, cast('2007-04-15' as date), cast('2009-08-22' as date), cast('2007-12-01' as date), 1), +(10019, 'Philadelphia', 5, cast('2007-05-16' as date), cast('2009-09-06' as date), cast('2007-12-01' as date), 3), +(10020, 'Philadelphia', 1, cast('2007-05-19' as date), cast('2009-08-26' as date), cast('2007-12-01' as date), 1), +(10021, 'Philadelphia', 7, cast('2007-05-10' as date), cast('2009-08-27' as date), cast('2007-12-01' as date), 3), +(10022, 'Philadelphia', 5, cast('2007-05-11' as date), cast('2009-08-14' as date), cast('2007-12-01' as date), 1), +(10023, 'Philadelphia', 1, cast('2007-05-11' as date), cast('2009-08-29' as date), cast('2007-12-01' as date), 1), +(10024, 'Philadelphia', 5, cast('2007-05-11' as date), cast('2009-08-29' as date), cast('2007-12-01' as date), 2), +(10025, 'Philadelphia', 6, cast('2007-05-12' as date), cast('2009-08-30' as date), cast('2007-12-01' as date), 2); + +CREATE TABLE roads.dispatchers ( + dispatcher_id int, + company_name string, + phone string +); +INSERT INTO roads.dispatchers VALUES +(1, 'Pothole Pete', '(111) 111-1111'), +(2, 'Asphalts R Us', '(222) 222-2222'), +(3, 'Federal Roads Group', '(333) 333-3333'), +(4, 'Local Patchers', '1-800-888-8888'), +(5, 'Gravel INC', '1-800-000-0000'), +(6, 'DJ Developers', '1-111-111-1111'); + +CREATE TABLE roads.contractors ( + contractor_id int, + company_name string, + contact_name string, + contact_title string, + address string, + city string, + state string, + postal_code string, + country string, + phone string +); +INSERT INTO roads.contractors VALUES +(1, 'You Need Em We Find Em', 'Max Potter', 'Assistant Director', '4 Plumb Branch Lane', 'Goshen', 'IN', '46526', 'USA', '(111) 111-1111'), +(2, 'Call Forwarding', 'Sylvester English', 'Administrator', '9650 Mill Lane', 'Raeford', 'NC', '28376', 'USA', '(222) 222-2222'), +(3, 'The Connect', 'Paul Raymond', 'Administrator', '7587 Myrtle Ave.', 'Chaska', 'MN', '55318', 'USA', '(333) 333-3333'); + +CREATE TABLE roads.us_region ( + us_region_id int, + us_region_description string +); +INSERT INTO roads.us_region VALUES +(1, 'Eastern'), +(2, 'Western'), +(3, 'Northern'), +(4, 'Southern'); + +CREATE TABLE roads.us_states ( + state_id int, + state_name string, + state_abbr string, + state_region string +); +INSERT INTO roads.us_states VALUES +(1, 'Alabama', 'AL', 'Southern'), +(2, 'Alaska', 'AK', 'Northern'), +(3, 'Arizona', 'AZ', 'Western'), +(4, 'Arkansas', 'AR', 'Southern'), +(5, 'California', 'CA', 'Western'), +(6, 'Colorado', 'CO', 'Western'), +(7, 'Connecticut', 'CT', 'Eastern'), +(8, 'Delaware', 'DE', 'Eastern'), +(9, 'District of Columbia', 'DC', 'Eastern'), +(10, 'Florida', 'FL', 'Southern'), +(11, 'Georgia', 'GA', 'Southern'), +(12, 'Hawaii', 'HI', 'Western'), +(13, 'Idaho', 'ID', 'Western'), +(14, 'Illinois', 'IL', 'Western'), +(15, 'Indiana', 'IN', 'Western'), +(16, 'Iowa', 'IO', 'Western'), +(17, 'Kansas', 'KS', 'Western'), +(18, 'Kentucky', 'KY', 'Southern'), +(19, 'Louisiana', 'LA', 'Southern'), +(20, 'Maine', 'ME', 'Northern'), +(21, 'Maryland', 'MD', 'Eastern'), +(22, 'Massachusetts', 'MA', 'Northern'), +(23, 'Michigan', 'MI', 'Northern'), +(24, 'Minnesota', 'MN', 'Northern'), +(25, 'Mississippi', 'MS', 'Southern'), +(26, 'Missouri', 'MO', 'Southern'), +(27, 'Montana', 'MT', 'Western'), +(28, 'Nebraska', 'NE', 'Western'), +(29, 'Nevada', 'NV', 'Western'), +(30, 'New Hampshire', 'NH', 'Eastern'), +(31, 'New Jersey', 'NJ', 'Eastern'), +(32, 'New Mexico', 'NM', 'Western'), +(33, 'New York', 'NY', 'Eastern'), +(34, 'North Carolina', 'NC', 'Eastern'), +(35, 'North Dakota', 'ND', 'Western'), +(36, 'Ohio', 'OH', 'Western'), +(37, 'Oklahoma', 'OK', 'Western'), +(38, 'Oregon', 'OR', 'Western'), +(39, 'Pennsylvania', 'PA', 'Eastern'), +(40, 'Rhode Island', 'RI', 'Eastern'), +(41, 'South Carolina', 'SC', 'Eastern'), +(42, 'South Dakota', 'SD', 'Western'), +(43, 'Tennessee', 'TN', 'Western'), +(44, 'Texas', 'TX', 'Western'), +(45, 'Utah', 'UT', 'Western'), +(46, 'Vermont', 'VT', 'Eastern'), +(47, 'Virginia', 'VA', 'Eastern'), +(48, 'Washington', 'WA', 'Western'), +(49, 'West Virginia', 'WV', 'Southern'), +(50, 'Wisconsin', 'WI', 'Western'), +(51, 'Wyoming', 'WY', 'Western'); + +CREATE TABLE roads.municipality_municipality_type ( + municipality_id string, + municipality_type_id string +); +INSERT INTO roads.municipality_municipality_type VALUES +('New York', 'A'), +('Los Angeles', 'B'), +('Chicago', 'B'), +('Houston', 'A'), +('Phoenix', 'A'), +('Philadelphia', 'B'), +('San Antonio', 'A'), +('San Diego', 'B'), +('Dallas', 'A'), +('San Jose', 'B'); + +CREATE TABLE roads.municipality_type ( + municipality_type_id string, + municipality_type_desc string +); +INSERT INTO roads.municipality_type VALUES +('A', 'Primary'), +('A', 'Secondary'); + +CREATE SCHEMA campaigns; + +CREATE TABLE campaigns.campaign_views ( + campaign_id int, + campaign_name string, + created_at timestamp, + views int +); + +INSERT INTO campaigns.campaign_views VALUES +(1, 'Clean Up Your Yard Marketing', epoch_ms(1526290800000), 120), +(2, 'Summer Sale Campaign', epoch_ms(1456472400000), 500), +(3, 'New Product Launch', epoch_ms(1341392400000), 250), +(4, 'Holiday Special Offers', epoch_ms(1451600400000), 800), +(5, 'Spring Cleaning Deals', epoch_ms(1472667600000), 300), +(6, 'Back to School Promotion', epoch_ms(1293848400000), 150), +(7, 'Winter Clearance Sale', epoch_ms(1512123600000), 900), +(8, 'Outdoor Adventure Campaign', epoch_ms(1288779600000), 400), +(9, 'Health and Wellness Expo', epoch_ms(1335795600000), 200), +(10, 'Summer Vacation Deals', epoch_ms(1462069200000), 600), +(11, 'Home Renovation Offers', epoch_ms(1396314000000), 350), +(12, 'Fashion Show Sponsorship', epoch_ms(1248478800000), 750), +(13, 'Charity Fundraising Drive', epoch_ms(1363563600000), 420), +(14, 'Tech Gadgets Showcase', epoch_ms(1433106000000), 230), +(15, 'Gourmet Food Festival', epoch_ms(1308032400000), 150), +(16, 'Music Concert Ticket Sales', epoch_ms(1380584400000), 550), +(17, 'Book Fair and Author Meet', epoch_ms(1262274000000), 180), +(18, 'Fitness Challenge Event', epoch_ms(1502053200000), 670), +(19, 'Pet Adoption Awareness', epoch_ms(1319638800000), 290), +(20, 'Art Exhibition Opening', epoch_ms(1409422800000), 390), +(21, 'Wedding Planning Expo', epoch_ms(1274245200000), 420), +(22, 'Sports Equipment Sale', epoch_ms(1419987600000), 780), +(23, 'Tech Startup Conference', epoch_ms(1370302800000), 210), +(24, 'Environmental Awareness', epoch_ms(1328053200000), 840), +(25, 'Travel and Adventure Expo', epoch_ms(1366832400000), 350), +(26, 'Automobile Showroom Launch', epoch_ms(1425162000000), 420), +(27, 'Film Festival Promotion', epoch_ms(1356997200000), 590), +(28, 'Summer Camp Enrollment', epoch_ms(1388778000000), 240), +(29, 'Online Shopping Festival', epoch_ms(1251776400000), 430), +(30, 'Healthcare Symposium', epoch_ms(1443642000000), 980); + +CREATE TABLE campaigns.email ( + campaign_id int, + email_address string, + email_id int, + last_contacted timestamp, + views int +); + +INSERT INTO campaigns.email VALUES +(1, 'mark@fakedomain.com', 1, to_timestamp(1451606400000), 2), +(1, 'john@fakedomain.com', 2, to_timestamp(1454284800000), 7), +(1, 'isaiah@fakedomain.com', 3, to_timestamp(1456790400000), 10), +(1, 'ruby@fakedomain.com', 4, to_timestamp(1459468800000), 5), +(1, 'oliver@fakedomain.com', 5, to_timestamp(1462060800000), 9), +(1, 'emma@fakedomain.com', 6, to_timestamp(1464739200000), 12), +(1, 'liam@fakedomain.com', 7, to_timestamp(1467331200000), 4), +(1, 'ava@fakedomain.com', 8, to_timestamp(1470009600000), 11), +(1, 'noah@fakedomain.com', 9, to_timestamp(1472688000000), 7), +(1, 'isabella@fakedomain.com', 10, to_timestamp(1475280000000), 3), +(2, 'sophia@fakedomain.com', 1, to_timestamp(1477958400000), 8), +(2, 'mason@fakedomain.com', 2, to_timestamp(1480550400000), 6), +(2, 'camila@fakedomain.com', 3, to_timestamp(1483228800000), 11), +(2, 'henry@fakedomain.com', 4, to_timestamp(1485907200000), 13), +(2, 'mia@fakedomain.com', 5, to_timestamp(1488326400000), 6), +(2, 'ethan@fakedomain.com', 6, to_timestamp(1491004800000), 9), +(2, 'lucas@fakedomain.com', 7, to_timestamp(1493596800000), 5), +(2, 'harper@fakedomain.com', 8, to_timestamp(1496275200000), 14), +(2, 'alexander@fakedomain.com', 9, to_timestamp(1498867200000), 12), +(2, 'abigail@fakedomain.com', 10, to_timestamp(1501545600000), 2), +(3, 'james@fakedomain.com', 1, to_timestamp(1504224000000), 7), +(3, 'amelia@fakedomain.com', 2, to_timestamp(1506816000000), 4), +(3, 'benjamin@fakedomain.com', 3, to_timestamp(1509494400000), 10), +(3, 'evelyn@fakedomain.com', 4, to_timestamp(1512086400000), 6), +(3, 'michael@fakedomain.com', 5, to_timestamp(1514764800000), 3), +(3, 'charlotte@fakedomain.com', 6, to_timestamp(1517443200000), 8), +(3, 'daniel@fakedomain.com', 7, to_timestamp(1520035200000), 12), +(3, 'harper@fakedomain.com', 8, to_timestamp(1522713600000), 9), +(3, 'lucas@fakedomain.com', 9, to_timestamp(1525305600000), 5), +(3, 'isabella@fakedomain.com', 10, to_timestamp(1527984000000), 11); + +CREATE TABLE campaigns.sms ( + campaign_id int, + phone_number string, + message string, + last_contacted timestamp, +); + +INSERT INTO campaigns.sms VALUES +(11, '(215) 111-1111', 'Click here to redeem 20% off!: http://smalllink', epoch_ms(1459468800000)), +(12, '(215) 222-2222', 'Interested in new lawn equipment?: http://smalllink', epoch_ms(1459468800001)), +(12, '(215) 333-3333', 'These sales will not last!: http://smalllink', epoch_ms(1459468800002)), +(13, '(215) 444-4444', 'Fall is here, enjoy half-off!: http://smalllink', epoch_ms(1459468800003)), +(14, '(215) 555-5555', 'Get the best deals today!: http://smalllink', epoch_ms(1459468800004)), +(15, '(215) 666-6666', 'Limited time offer - 30% off!: http://smalllink', epoch_ms(1459468800005)), +(16, '(215) 777-7777', 'Do not miss out on our sale!: http://smalllink', epoch_ms(1459468800006)), +(17, '(215) 888-8888', 'Exclusive discount for you!: http://smalllink', epoch_ms(1459468800007)), +(18, '(215) 999-9999', 'Shop now and save big!: http://smalllink', epoch_ms(1459468800008)), +(19, '(215) 000-0000', 'Huge clearance sale - up to 50% off!: http://smalllink', epoch_ms(1459468800009)), +(10, '(215) 123-4567', 'New arrivals with special discounts!: http://smalllink', epoch_ms(1459468800010)), +(11, '(215) 234-5678', 'Save money with our promo codes!: http://smalllink', epoch_ms(1459468800011)), +(12, '(215) 345-6789', 'Enjoy free shipping on all orders!: http://smalllink', epoch_ms(1459468800012)), +(13, '(215) 456-7890', 'Limited stock available - act fast!: http://smalllink', epoch_ms(1459468800013)), +(14, '(215) 567-8901', 'Upgrade your home with our deals!: http://smalllink', epoch_ms(1459468800014)), +(15, '(215) 678-9012', 'Special discount for loyal customers!: http://smalllink', epoch_ms(1459468800015)), +(16, '(215) 789-0123', 'Do not miss our summer sale!: http://smalllink', epoch_ms(1459468800016)), +(17, '(215) 890-1234', 'Exclusive offer - limited time only!: http://smalllink', epoch_ms(1459468800017)), +(18, '(215) 901-2345', 'Shop now and get a free gift!: http://smalllink', epoch_ms(1459468800018)), +(19, '(215) 012-3456', 'Big discounts on popular brands!: http://smalllink', epoch_ms(1459468800019)), +(10, '(215) 987-6543', 'Save up to 70% off on selected items!: http://smalllink', epoch_ms(1459468800020)), +(11, '(215) 876-5432', 'Limited time offer - buy one, get one free!: http://smalllink', epoch_ms(1459468800021)), +(12, '(215) 765-4321', 'Great deals for your next vacation!: http://smalllink', epoch_ms(1459468800022)), +(13, '(215) 654-3210', 'Get ready for the holiday season with our discounts!: http://smalllink', epoch_ms(1459468800023)), +(14, '(215) 543-2109', 'Save on electronics and gadgets!: http://smalllink', epoch_ms(1459468800024)), +(15, '(215) 432-1098', 'Limited stock available - you do not want to miss out!: http://smalllink', epoch_ms(1459468800025)), +(16, '(215) 321-0987', 'Shop now and enjoy free returns!: http://smalllink', epoch_ms(1459468800026)), +(17, '(215) 210-9876', 'Exclusive discount for online orders!: http://smalllink', epoch_ms(1459468800027)), +(18, '(215) 109-8765', 'Upgrade your wardrobe with our sale!: http://smalllink', epoch_ms(1459468800028)), +(19, '(215) 098-7654', 'Will you miss our clearance sale??: http://smalllink', epoch_ms(1459468800029)), +(10, '(215) 987-6543', 'Get the best deals for your home!: http://smalllink', epoch_ms(1459468800030)), +(12, '(215) 222-2222', 'New collection now available!: http://smalllink', epoch_ms(1459468800002)), +(13, '(215) 333-3333', 'Limited stock - dont miss out!: http://smalllink', epoch_ms(1459468800003)), +(14, '(215) 444-4444', 'Free shipping on all orders!: http://smalllink', epoch_ms(1459468800004)), +(15, '(215) 555-5555', 'Sign up for exclusive offers!: http://smalllink', epoch_ms(1459468800005)), +(16, '(215) 666-6666', 'Get ready for summer with our new arrivals!: http://smalllink', epoch_ms(1459468800006)), +(17, '(215) 777-7777', 'Limited time sale - up to 60% off!: http://smalllink', epoch_ms(1459468800007)), +(10, '(215) 111-2222', 'Shop now and receive a gift card!: http://smalllink', epoch_ms(1459468800010)), +(11, '(215) 222-3333', 'Final clearance - last chance to save!: http://smalllink', epoch_ms(1459468800011)), +(12, '(215) 333-4444', 'Get the latest fashion trends at discounted prices!: http://smalllink', epoch_ms(1459468800012)), +(13, '(215) 444-5555', 'Upgrade your electronics with our special offers!: http://smalllink', epoch_ms(1459468800013)), +(14, '(215) 555-6666', 'Big savings on home appliances!: http://smalllink', epoch_ms(1459468800014)), +(15, '(215) 666-7777', 'Limited stock - shop now before its gone!: http://smalllink', epoch_ms(1459468800015)), +(16, '(215) 777-8888', 'Get the best deals on beauty products!: http://smalllink', epoch_ms(1459468800016)), +(11, '(215) 012-3456', 'Dont miss our summer sale!: http://smalllink', epoch_ms(1459468800021)), +(12, '(215) 123-4567', 'Exclusive offer for our loyal customers!: http://smalllink', epoch_ms(1459468800022)), +(13, '(215) 234-5678', 'Shop now and enjoy free returns!: http://smalllink', epoch_ms(1459468800023)), +(14, '(215) 345-6789', 'Upgrade your gaming setup with our deals!: http://smalllink', epoch_ms(1459468800024)), +(15, '(215) 456-7890', 'Save on outdoor essentials for your next adventure!: http://smalllink', epoch_ms(1459468800025)), +(16, '(215) 567-8901', 'Limited time offer - buy one, get one free!: http://smalllink', epoch_ms(1459468800026)), +(19, '(215) 098-7654', 'Discover the latest tech gadgets at discounted prices!: http://smalllink', epoch_ms(1459468800029)), +(12, '(215) 876-5432', 'Get the perfect gift for your loved ones!: http://smalllink', epoch_ms(1459468800031)); + +CREATE TABLE campaigns.commercial ( + campaign_id int, + station string, + last_played timestamp, +); + +INSERT INTO campaigns.commercial VALUES +(23, 'Montgomery Oldies Station', epoch_ms(1688879124599)), +(21, 'Lithonia Jazz & Blues', epoch_ms(1688879124599)), +(20, 'Manchester 90s R&B', epoch_ms(1688879124599)), +(22, 'Los Angeles Rock Hits', epoch_ms(1688879124599)), +(23, 'Chicago Pop Mix', epoch_ms(1688879124599)), +(24, 'Houston Country Station', epoch_ms(1688879124599)), +(25, 'New York Hip Hop', epoch_ms(1688879124599)), +(26, 'Seattle Alternative Rock', epoch_ms(1688879124599)), +(27, 'Miami Latin Beats', epoch_ms(1688879124599)), +(28, 'Denver Indie Folk', epoch_ms(1688879124599)), +(29, 'Boston Classical', epoch_ms(1688879124599)), +(20, 'San Francisco Electronic', epoch_ms(1688879124599)), +(21, 'Philadelphia R&B Hits', epoch_ms(1688879124599)), +(22, 'Dallas Pop Punk', epoch_ms(1688879124599)), +(23, 'Atlanta Gospel', epoch_ms(1688879124599)), +(24, 'Las Vegas Hard Rock', epoch_ms(1688879124599)), +(25, 'Phoenix Country Hits', epoch_ms(1688879124599)), +(26, 'Portland Alternative', epoch_ms(1688879124599)), +(27, 'Austin Indie Rock', epoch_ms(1688879124599)), +(28, 'Nashville Country Classics', epoch_ms(1688879124599)), +(29, 'San Diego Surf Rock', epoch_ms(1688879124599)), +(20, 'Minneapolis Folk', epoch_ms(1688879124599)), +(21, 'Detroit Motown', epoch_ms(1688879124599)), +(22, 'Baltimore Jazz Lounge', epoch_ms(1688879124599)), +(23, 'Kansas City Blues', epoch_ms(1688879124599)), +(24, 'St. Louis Smooth Jazz', epoch_ms(1688879124599)), +(25, 'Cleveland Classic Rock', epoch_ms(1688879124599)), +(26, 'Pittsburgh Metal', epoch_ms(1688879124599)), +(27, 'Charlotte Pop Hits', epoch_ms(1688879124599)), +(28, 'Raleigh-Durham Indie Pop', epoch_ms(1688879124599)), +(29, 'Tampa Bay Reggae', epoch_ms(1688879124599)); + +CREATE SCHEMA games; + +CREATE TABLE games.titles ( + game_id int, + game_name string, + num_distinct_players int, + release_date timestamp, + platform string, + genre string, + publisher_id int, + developer_id int, + average_rating float, + online_mode boolean, + total_sales int, + active_monthly int, + dlcs int +); + +INSERT INTO games.titles ( + game_id, + game_name, + num_distinct_players, + release_date, + platform, + genre, + publisher_id, + developer_id, + average_rating, + online_mode, + total_sales, + active_monthly, + dlcs +) +VALUES + (1, 'Battle of the Chinchillas: Furry Fury', 1000, '2022-01-01 00:00:00', 'PS5', 'Action', 1, 928, 4.5, true, 1000000, 5000, 3), + (2, 'Grand Theft Toaster: Carb City Chronicles', 500, '2021-06-15 00:00:00', 'Xbox Series X', 'RPG', 1, 948, 4.2, false, 750000, 2500, 2), + (3, 'Super Slime Soccer: Gooey Goalkeepers', 2000, '2020-11-30 00:00:00', 'Nintendo Switch', 'Sports', 2, 937, 4.8, true, 500000, 3000, 4), + (4, 'Dance Dance Avocado: Guacamole Groove', 1500, '2022-08-20 00:00:00', 'PC', 'Rhythm', 22, 987, 4.6, true, 250000, 2000, 1), + (5, 'Zombie Zookeeper: Undead Menagerie', 800, '2023-02-10 00:00:00', 'PS4', 'Strategy', 13, 902, 4.4, false, 300000, 1500, 3), + (6, 'Squirrel Simulator: Nutty Adventure', 3000, '2021-03-05 00:00:00', 'Xbox One', 'Simulation', 15, 928, 4.2, true, 400000, 2500, 2), + (7, 'Crash Test Dummies: Wacky Collision', 1200, '2022-05-15 00:00:00', 'PC', 'Action', 12, 928, 4.7, false, 150000, 1000, 1), + (8, 'Pizza Delivery Panic: Cheesy Chaos', 1000, '2023-01-30 00:00:00', 'Nintendo Switch', 'Arcade', 8, 928, 4.3, true, 200000, 1800, 2), + (9, 'Alien Abduction Academy: Extraterrestrial Education', 2500, '2020-09-05 00:00:00', 'PS5', 'Adventure', 7, 987, 4.5, false, 800000, 3500, 3), + (10, 'Crazy Cat Circus: Meow Mayhem', 700, '2022-07-25 00:00:00', 'Xbox Series X', 'Puzzle', 18, 987, 4.1, true, 100000, 1200, 1), + (11, 'Robot Rampage: Mechanical Mayhem', 1800, '2021-04-12 00:00:00', 'PC', 'Action', 4, 987, 4.6, true, 450000, 2200, 2), + (12, 'Super Spy Squirrels: Nutty Espionage', 900, '2023-03-18 00:00:00', 'PS4', 'Stealth', 10, 934, 4.4, false, 400000, 1800, 3), + (13, 'Banana Blaster: Fruit Frenzy', 2800, '2021-02-08 00:00:00', 'Xbox One', 'Shooter', 10, 934, 4.3, true, 700000, 3000, 2), + (14, 'Penguin Paradise: Antarctic Adventure', 1100, '2022-04-05 00:00:00', 'PC', 'Simulation', 20, 902, 4.7, false, 200000, 1200, 1), + (15, 'Unicorn Universe: Rainbow Realm', 500, '2023-01-15 00:00:00', 'Nintendo Switch', 'Adventure', 1, 902, 4.2, true, 150000, 900, 2), + (16, 'Spaghetti Showdown: Saucy Shootout', 2100, '2020-10-20 00:00:00', 'PS5', 'Action', 4, 902, 4.4, true, 550000, 2500, 3), + (17, 'Bubblegum Bandits: Sticky Heist', 800, '2022-06-10 00:00:00', 'Xbox Series X', 'Stealth', 3, 902, 4.1, false, 180000, 800, 1), + (18, 'Safari Slingshot: Wild Wildlife', 1300, '2021-03-01 00:00:00', 'PC', 'Arcade', 3, 987, 4.6, true, 300000, 1500, 2), + (19, 'Monster Mop: Cleaning Catastrophe', 600, '2022-12-20 00:00:00', 'Nintendo Switch', 'Puzzle', 17, 934, 4.3, false, 120000, 700, 1), + (20, 'Galactic Golf: Space Swing', 1900, '2020-08-15 00:00:00', 'PS4', 'Sports', 5, 921, 4.5, true, 400000, 2000, 3), + (21, 'Funky Farm Friends: Groovy Gardening', 900, '2023-02-05 00:00:00', 'Xbox One', 'Simulation', 21, 921, 4.3, true, 250000, 1300, 2), + (22, 'Cake Crusaders: Sugary Siege', 1400, '2021-01-25 00:00:00', 'PC', 'Strategy', 23, 902, 4.7, false, 180000, 900, 1), + (23, 'Ninja Narwhal: Aquatic Assassin', 1000, '2022-03-15 00:00:00', 'Nintendo Switch', 'Action', 23, 901, 4.2, true, 150000, 1000, 2); + +CREATE TABLE games.publishers ( + publisher_id int PRIMARY KEY, + publisher_name string +); + +INSERT INTO games.publishers (publisher_id, publisher_name) +VALUES + (1, 'Wacky Game Studios'), + (2, 'Silly Monkey Games'), + (3, 'Laughing Unicorn Interactive'), + (4, 'Crazy Cat Games'), + (5, 'Quirky Penguin Productions'), + (6, 'Absurd Antelope Studios'), + (7, 'Hilarious Hedgehog Games'), + (8, 'Goofy Giraffe Studios'), + (9, 'Whimsical Walrus Entertainment'), + (10, 'Zany Zebra Games'), + (11, 'Ridiculous Rabbit Studios'), + (12, 'Funny Fox Interactive'), + (13, 'Surreal Snake Games'), + (14, 'Bizarre Bat Studios'), + (15, 'Madcap Moose Productions'), + (16, 'Cuckoo Clock Games'), + (17, 'Lunatic Llama Studios'), + (18, 'Silly Goose Games'), + (19, 'Bonkers Beaver Interactive'), + (20, 'Witty Walrus Games'); + +CREATE TABLE games.developers ( + developer_id int, + developer_name string, + num_games_developed int +); + +INSERT INTO games.developers (developer_id, developer_name, num_games_developed) +VALUES + (928, 'CrazyCodr', 10), + (948, 'PixelPirate', 5), + (937, 'CodeNinja', 3), + (987, 'GameWizard', 8), + (902, 'ByteBender', 15), + (934, 'CodeJester', 4), + (902, 'MadGenius', 12), + (921, 'GameGuru', 9), + (901, 'ScriptMage', 6); \ No newline at end of file diff --git a/datajunction-query/docker/duckdb_load.py b/datajunction-query/docker/duckdb_load.py new file mode 100644 index 000000000..163eab834 --- /dev/null +++ b/datajunction-query/docker/duckdb_load.py @@ -0,0 +1,11 @@ +import duckdb + +con = duckdb.connect("default.duckdb") + +with open("duckdb.sql", "r") as f: + queries = f.read().split(";") + +for q in queries: + if q.strip(): + con.sql(q) + print("Query executed successfully") diff --git a/datajunction-query/docker/postgres_init.roads.sql b/datajunction-query/docker/postgres_init.roads.sql new file mode 100644 index 000000000..24a28a63f --- /dev/null +++ b/datajunction-query/docker/postgres_init.roads.sql @@ -0,0 +1,273 @@ +DROP DATABASE IF EXISTS djdb; +CREATE DATABASE djdb; + +\connect djdb; + +CREATE SCHEMA IF NOT EXISTS roads; + +DROP TABLE IF EXISTS roads.municipality_municipality_type; +DROP TABLE IF EXISTS roads.municipality_type; +DROP TABLE IF EXISTS roads.repair_order_details; +DROP TABLE IF EXISTS roads.repair_orders; +DROP TABLE IF EXISTS roads.municipality; +DROP TABLE IF EXISTS roads.repair_type; +DROP TABLE IF EXISTS roads.dispatchers; +DROP TABLE IF EXISTS roads.contractors; +DROP TABLE IF EXISTS roads.us_states; +DROP TABLE IF EXISTS roads.us_region; +DROP TABLE IF EXISTS roads.hard_hats; +DROP TABLE IF EXISTS roads.hard_hat_state; + +CREATE TABLE roads.repair_type ( + repair_type_id smallint NOT NULL, + repair_type_name character varying(50) NOT NULL, + contractor_id text +); +INSERT INTO roads.repair_type VALUES (1, 'Asphalt Overlay', 'Asphalt overlays restore roads to a smooth condition. This resurfacing uses the deteriorating asphalt as a base for which the new layer is added on top of, instead of tearing up the worsening one.'); +INSERT INTO roads.repair_type VALUES (2, 'Patching', 'Patching is the process of filling potholes or excavated areas in the asphalt pavement. Quick repair of potholes or other pavement disintegration helps control further deterioration and expensive repair of the pavement. Without timely patching, water can enter the sub-grade and cause larger and more serious pavement failures.'); +INSERT INTO roads.repair_type VALUES (3, 'Reshaping', 'This is necessary when a road surface it too damaged to be smoothed. Using a grader blade and scarifying if necessary, you rework the gravel sub-base to eliminate large potholes and rebuild a flattened crown.'); +INSERT INTO roads.repair_type VALUES (4, 'Slab Replacement', 'This refers to replacing sections of paved roads. It is a good option for when slabs are chipped, cracked, or uneven, and mitigates the need to replace the entire road when just a small section is damaged.'); +INSERT INTO roads.repair_type VALUES (5, 'Smoothing', 'This is when you lightly rework the gravel of a road without digging in too far to the sub-base. Typically, a motor grader is used in this operation with an attached blade. Smoothing is done when the road has minor damage or is just worn down a bit from use.'); +INSERT INTO roads.repair_type VALUES (6, 'Reconstruction', 'When roads have deteriorated to a point that it is no longer cost-effective to maintain, the entire street or road needs to be rebuilt. Typically, this work is done in phases to limit traffic restrictions. As part of reconstruction, the street may be realigned to improve safety or operations, grading may be changed to improve storm water flow, underground utilities may be added, upgraded or relocated, traffic signals and street lights may be relocated, and street trees and pedestrian ramps may be added.'); + +CREATE TABLE roads.municipality ( + municipality_id character varying(20) NOT NULL, + contact_name character varying(30), + contact_title character varying(50), + local_region character varying(30), + state_id smallint NOT NULL +); +INSERT INTO roads.municipality VALUES ('New York', 'Alexander Wilkinson', 'Assistant City Clerk', 'Manhattan', 33); +INSERT INTO roads.municipality VALUES ('Los Angeles', 'Hugh Moser', 'Administrative Assistant', 'Santa Monica',5 ); +INSERT INTO roads.municipality VALUES ('Chicago', 'Phillip Bradshaw', 'Director of Community Engagement', 'West Ridge', 14); +INSERT INTO roads.municipality VALUES ('Houston', 'Leo Ackerman', 'Municipal Roads Specialist', 'The Woodlands', 44); +INSERT INTO roads.municipality VALUES ('Phoenix', 'Jessie Paul', 'Director of Finance and Administration', 'Old Town Scottsdale', 3); +INSERT INTO roads.municipality VALUES ('Philadelphia', 'Willie Chaney', 'Municipal Manager', 'Center City', 39); +INSERT INTO roads.municipality VALUES ('San Antonio', 'Chester Lyon', 'Treasurer', 'Alamo Heights', 44); +INSERT INTO roads.municipality VALUES ('San Diego', 'Ralph Helms', 'Senior Electrical Project Manager', 'Del Mar', 5); +INSERT INTO roads.municipality VALUES ('Dallas', 'Virgil Craft', 'Assistant Assessor (Town/Municipality)', 'Deep Ellum', 44); +INSERT INTO roads.municipality VALUES ('San Jose', 'Charles Carney', 'Municipal Accounting Manager', 'Santana Row', 5); + +CREATE TABLE roads.hard_hats ( + hard_hat_id smallint NOT NULL, + last_name character varying(20) NOT NULL, + first_name character varying(10) NOT NULL, + title character varying(30), + birth_date date, + hire_date date, + address character varying(60), + city character varying(15), + state character varying(15), + postal_code character varying(10), + country character varying(15), + manager smallint, + contractor_id smallint +); +INSERT INTO roads.hard_hats VALUES (1, 'Brian', 'Perkins', 'Construction Laborer', '1978-11-28', '2009-02-06', '4 Jennings Ave.', 'Jersey City', 'NJ', '37421', 'USA', 9, 1); +INSERT INTO roads.hard_hats VALUES (2, 'Nicholas', 'Massey', 'Carpenter', '1993-02-19', '2003-04-14', '9373 Southampton Street', 'Middletown', 'CT', '27292', 'USA', 9, 1); +INSERT INTO roads.hard_hats VALUES (3, 'Cathy', 'Best', 'Framer', '1994-08-30', '1990-07-02', '4 Hillside Street', 'Billerica', 'MA', '13440', 'USA', 9, 2); +INSERT INTO roads.hard_hats VALUES (4, 'Melanie', 'Stafford', 'Construction Manager', '1966-03-19', '2003-02-02', '77 Studebaker Lane', 'Southampton', 'PA', '71730', 'USA', 9, 2); +INSERT INTO roads.hard_hats VALUES (5, 'Donna', 'Riley', 'Pre-construction Manager', '1983-03-14', '2012-01-13', '82 Taylor Drive', 'Southgate', 'MI', '33125', 'USA', 9, 4); +INSERT INTO roads.hard_hats VALUES (6, 'Alfred', 'Clarke', 'Construction Superintendent', '1979-01-12', '2013-10-17', '7729 Catherine Street', 'Powder Springs', 'GA', '42001', 'USA', 9, 2); +INSERT INTO roads.hard_hats VALUES (7, 'William', 'Boone', 'Construction Laborer', '1970-02-28', '2013-01-02', '1 Border St.', 'Niagara Falls', 'NY', '14304', 'USA', 9, 4); +INSERT INTO roads.hard_hats VALUES (8, 'Luka', 'Henderson', 'Construction Laborer', '1988-12-09', '2013-03-05', '794 S. Chapel Ave.', 'Phoenix', 'AZ', '85021', 'USA', 9, 1); +INSERT INTO roads.hard_hats VALUES (9, 'Patrick', 'Ziegler', 'Construction Laborer', '1976-11-27', '2020-11-15', '321 Gainsway Circle', 'Muskogee', 'OK', '74403', 'USA', 9, 3); + +CREATE TABLE roads.hard_hat_state ( + hard_hat_id smallint NOT NULL, + state_id smallint NOT NULL +); +INSERT INTO roads.hard_hat_state VALUES (1, 2); +INSERT INTO roads.hard_hat_state VALUES (2, 32); +INSERT INTO roads.hard_hat_state VALUES (3, 28); +INSERT INTO roads.hard_hat_state VALUES (4, 12); +INSERT INTO roads.hard_hat_state VALUES (5, 5); +INSERT INTO roads.hard_hat_state VALUES (6, 3); +INSERT INTO roads.hard_hat_state VALUES (7, 16); +INSERT INTO roads.hard_hat_state VALUES (8, 32); +INSERT INTO roads.hard_hat_state VALUES (9, 41); + +CREATE TABLE roads.repair_order_details ( + repair_order_id smallint NOT NULL, + repair_type_id smallint NOT NULL, + price real NOT NULL, + quantity smallint NOT NULL, + discount real NOT NULL +); +INSERT INTO roads.repair_order_details VALUES (10001, 1, 63708, 1, 0.05); +INSERT INTO roads.repair_order_details VALUES (10002, 4, 67253, 1, 0.05); +INSERT INTO roads.repair_order_details VALUES (10003, 2, 66808, 1, 0.05); +INSERT INTO roads.repair_order_details VALUES (10004, 4, 18497, 1, 0.05); +INSERT INTO roads.repair_order_details VALUES (10005, 7, 76463, 1, 0.05); +INSERT INTO roads.repair_order_details VALUES (10006, 4, 87858, 1, 0.05); +INSERT INTO roads.repair_order_details VALUES (10007, 1, 63918, 1, 0.05); +INSERT INTO roads.repair_order_details VALUES (10008, 6, 21083, 1, 0.05); +INSERT INTO roads.repair_order_details VALUES (10009, 3, 74555, 1, 0.05); +INSERT INTO roads.repair_order_details VALUES (10010, 5, 27222, 1, 0.05); +INSERT INTO roads.repair_order_details VALUES (10011, 5, 73600, 1, 0.05); +INSERT INTO roads.repair_order_details VALUES (10012, 3, 54901, 1, 0.01); +INSERT INTO roads.repair_order_details VALUES (10013, 5, 51594, 1, 0.01); +INSERT INTO roads.repair_order_details VALUES (10014, 1, 65114, 1, 0.01); +INSERT INTO roads.repair_order_details VALUES (10015, 1, 48919, 1, 0.01); +INSERT INTO roads.repair_order_details VALUES (10016, 3, 70418, 1, 0.01); +INSERT INTO roads.repair_order_details VALUES (10017, 1, 29684, 1, 0.01); +INSERT INTO roads.repair_order_details VALUES (10018, 2, 62928, 1, 0.01); +INSERT INTO roads.repair_order_details VALUES (10019, 2, 97916, 1, 0.01); +INSERT INTO roads.repair_order_details VALUES (10020, 5, 44120, 1, 0.01); +INSERT INTO roads.repair_order_details VALUES (10021, 1, 53374, 1, 0.01); +INSERT INTO roads.repair_order_details VALUES (10022, 2, 87289, 1, 0.01); +INSERT INTO roads.repair_order_details VALUES (10023, 2, 92366, 1, 0.01); +INSERT INTO roads.repair_order_details VALUES (10024, 2, 47857, 1, 0.01); +INSERT INTO roads.repair_order_details VALUES (10025, 1, 68745, 1, 0.01); + +CREATE TABLE roads.repair_orders ( + repair_order_id smallint NOT NULL, + municipality_id character varying(20), + hard_hat_id smallint, + order_date date, + required_date date, + dispatched_date date, + dispatcher_id smallint +); +INSERT INTO roads.repair_orders VALUES (10001, 'New York', 1, '2007-07-04', '2009-07-18', '2007-12-01', 3); +INSERT INTO roads.repair_orders VALUES (10002, 'New York', 3, '2007-07-05', '2009-08-28', '2007-12-01', 1); +INSERT INTO roads.repair_orders VALUES (10003, 'New York', 5, '2007-07-08', '2009-08-12', '2007-12-01', 2); +INSERT INTO roads.repair_orders VALUES (10004, 'Dallas', 1, '2007-07-08', '2009-08-01', '2007-12-01', 1); +INSERT INTO roads.repair_orders VALUES (10005, 'San Antonio', 8, '2007-07-09', '2009-08-01', '2007-12-01', 2); +INSERT INTO roads.repair_orders VALUES (10006, 'New York', 3, '2007-07-10', '2009-08-01', '2007-12-01', 2); +INSERT INTO roads.repair_orders VALUES (10007, 'Philadelphia', 4, '2007-04-21', '2009-08-08', '2007-12-01', 2); +INSERT INTO roads.repair_orders VALUES (10008, 'Philadelphia', 5, '2007-04-22', '2009-08-09', '2007-12-01', 3); +INSERT INTO roads.repair_orders VALUES (10009, 'Philadelphia', 3, '2007-04-25', '2009-08-12', '2007-12-01', 2); +INSERT INTO roads.repair_orders VALUES (10010, 'Philadelphia', 4, '2007-04-26', '2009-08-13', '2007-12-01', 3); +INSERT INTO roads.repair_orders VALUES (10011, 'Philadelphia', 4, '2007-04-27', '2009-08-14', '2007-12-01', 1); +INSERT INTO roads.repair_orders VALUES (10012, 'Philadelphia', 8, '2007-04-28', '2009-08-15', '2007-12-01', 3); +INSERT INTO roads.repair_orders VALUES (10013, 'Philadelphia', 4, '2007-04-29', '2009-08-16', '2007-12-01', 1); +INSERT INTO roads.repair_orders VALUES (10014, 'Philadelphia', 6, '2007-04-29', '2009-08-16', '2007-12-01', 2); +INSERT INTO roads.repair_orders VALUES (10015, 'Philadelphia', 2, '2007-04-12', '2009-08-19', '2007-12-01', 3); +INSERT INTO roads.repair_orders VALUES (10016, 'Philadelphia', 9, '2007-04-13', '2009-08-20', '2007-12-01', 3); +INSERT INTO roads.repair_orders VALUES (10017, 'Philadelphia', 2, '2007-04-14', '2009-08-21', '2007-12-01', 3); +INSERT INTO roads.repair_orders VALUES (10018, 'Philadelphia', 6, '2007-04-15', '2009-08-22', '2007-12-01', 1); +INSERT INTO roads.repair_orders VALUES (10019, 'Philadelphia', 5, '2007-05-16', '2009-09-06', '2007-12-01', 3); +INSERT INTO roads.repair_orders VALUES (10020, 'Philadelphia', 1, '2007-05-19', '2009-08-26', '2007-12-01', 1); +INSERT INTO roads.repair_orders VALUES (10021, 'Philadelphia', 7, '2007-05-10', '2009-08-27', '2007-12-01', 3); +INSERT INTO roads.repair_orders VALUES (10022, 'Philadelphia', 5, '2007-05-11', '2009-08-14', '2007-12-01', 1); +INSERT INTO roads.repair_orders VALUES (10023, 'Philadelphia', 1, '2007-05-11', '2009-08-29', '2007-12-01', 1); +INSERT INTO roads.repair_orders VALUES (10024, 'Philadelphia', 5, '2007-05-11', '2009-08-29', '2007-12-01', 2); +INSERT INTO roads.repair_orders VALUES (10025, 'Philadelphia', 6, '2007-05-12', '2009-08-30', '2007-12-01', 2); + +CREATE TABLE roads.dispatchers ( + dispatcher_id smallint NOT NULL, + company_name character varying(40) NOT NULL, + phone character varying(24) +); +INSERT INTO roads.dispatchers VALUES (1, 'Pothole Pete', '(111) 111-1111'); +INSERT INTO roads.dispatchers VALUES (2, 'Asphalts R Us', '(222) 222-2222'); +INSERT INTO roads.dispatchers VALUES (3, 'Federal Roads Group', '(333) 333-3333'); +INSERT INTO roads.dispatchers VALUES (4, 'Local Patchers', '1-800-888-8888'); +INSERT INTO roads.dispatchers VALUES (5, 'Gravel INC', '1-800-000-0000'); +INSERT INTO roads.dispatchers VALUES (6, 'DJ Developers', '1-111-111-1111'); + +CREATE TABLE roads.contractors ( + contractor_id smallint NOT NULL, + company_name character varying(40) NOT NULL, + contact_name character varying(30), + contact_title character varying(30), + address character varying(60), + city character varying(15), + state character varying(15), + postal_code character varying(10), + country character varying(15), + phone character varying(24) +); +INSERT INTO roads.contractors VALUES (1, 'You Need Em We Find Em', 'Max Potter', 'Assistant Director', '4 Plumb Branch Lane', 'Goshen', 'IN', '46526', 'USA', '(111) 111-1111'); +INSERT INTO roads.contractors VALUES (2, 'Call Forwarding', 'Sylvester English', 'Administrator', '9650 Mill Lane', 'Raeford', 'NC', '28376', 'USA', '(222) 222-2222'); +INSERT INTO roads.contractors VALUES (3, 'The Connect', 'Paul Raymond', 'Administrator', '7587 Myrtle Ave.', 'Chaska', 'MN', '55318', 'USA', '(333) 333-3333'); + +CREATE TABLE roads.us_region ( + us_region_id smallint NOT NULL, + us_region_description character varying(60) NOT NULL +); +INSERT INTO roads.us_region VALUES (1, 'Eastern'); +INSERT INTO roads.us_region VALUES (2, 'Western'); +INSERT INTO roads.us_region VALUES (3, 'Northern'); +INSERT INTO roads.us_region VALUES (4, 'Southern'); + +CREATE TABLE roads.us_states ( + state_id smallint NOT NULL, + state_name character varying(100), + state_abbr character varying(2), + state_region character varying(50) +); +INSERT INTO roads.us_states VALUES (1, 'Alabama', 'AL', 'Southern'); +INSERT INTO roads.us_states VALUES (2, 'Alaska', 'AK', 'Northern'); +INSERT INTO roads.us_states VALUES (3, 'Arizona', 'AZ', 'Western'); +INSERT INTO roads.us_states VALUES (4, 'Arkansas', 'AR', 'Southern'); +INSERT INTO roads.us_states VALUES (5, 'California', 'CA', 'Western'); +INSERT INTO roads.us_states VALUES (6, 'Colorado', 'CO', 'Western'); +INSERT INTO roads.us_states VALUES (7, 'Connecticut', 'CT', 'Eastern'); +INSERT INTO roads.us_states VALUES (8, 'Delaware', 'DE', 'Eastern'); +INSERT INTO roads.us_states VALUES (9, 'District of Columbia', 'DC', 'Eastern'); +INSERT INTO roads.us_states VALUES (10, 'Florida', 'FL', 'Southern'); +INSERT INTO roads.us_states VALUES (11, 'Georgia', 'GA', 'Southern'); +INSERT INTO roads.us_states VALUES (12, 'Hawaii', 'HI', 'Western'); +INSERT INTO roads.us_states VALUES (13, 'Idaho', 'ID', 'Western'); +INSERT INTO roads.us_states VALUES (14, 'Illinois', 'IL', 'Western'); +INSERT INTO roads.us_states VALUES (15, 'Indiana', 'IN', 'Western'); +INSERT INTO roads.us_states VALUES (16, 'Iowa', 'IO', 'Western'); +INSERT INTO roads.us_states VALUES (17, 'Kansas', 'KS', 'Western'); +INSERT INTO roads.us_states VALUES (18, 'Kentucky', 'KY', 'Southern'); +INSERT INTO roads.us_states VALUES (19, 'Louisiana', 'LA', 'Southern'); +INSERT INTO roads.us_states VALUES (20, 'Maine', 'ME', 'Northern'); +INSERT INTO roads.us_states VALUES (21, 'Maryland', 'MD', 'Eastern'); +INSERT INTO roads.us_states VALUES (22, 'Massachusetts', 'MA', 'Northern'); +INSERT INTO roads.us_states VALUES (23, 'Michigan', 'MI', 'Northern'); +INSERT INTO roads.us_states VALUES (24, 'Minnesota', 'MN', 'Northern'); +INSERT INTO roads.us_states VALUES (25, 'Mississippi', 'MS', 'Southern'); +INSERT INTO roads.us_states VALUES (26, 'Missouri', 'MO', 'Southern'); +INSERT INTO roads.us_states VALUES (27, 'Montana', 'MT', 'Western'); +INSERT INTO roads.us_states VALUES (28, 'Nebraska', 'NE', 'Western'); +INSERT INTO roads.us_states VALUES (29, 'Nevada', 'NV', 'Western'); +INSERT INTO roads.us_states VALUES (30, 'New Hampshire', 'NH', 'Eastern'); +INSERT INTO roads.us_states VALUES (31, 'New Jersey', 'NJ', 'Eastern'); +INSERT INTO roads.us_states VALUES (32, 'New Mexico', 'NM', 'Western'); +INSERT INTO roads.us_states VALUES (33, 'New York', 'NY', 'Eastern'); +INSERT INTO roads.us_states VALUES (34, 'North Carolina', 'NC', 'Eastern'); +INSERT INTO roads.us_states VALUES (35, 'North Dakota', 'ND', 'Western'); +INSERT INTO roads.us_states VALUES (36, 'Ohio', 'OH', 'Western'); +INSERT INTO roads.us_states VALUES (37, 'Oklahoma', 'OK', 'Western'); +INSERT INTO roads.us_states VALUES (38, 'Oregon', 'OR', 'Western'); +INSERT INTO roads.us_states VALUES (39, 'Pennsylvania', 'PA', 'Eastern'); +INSERT INTO roads.us_states VALUES (40, 'Rhode Island', 'RI', 'Eastern'); +INSERT INTO roads.us_states VALUES (41, 'South Carolina', 'SC', 'Eastern'); +INSERT INTO roads.us_states VALUES (42, 'South Dakota', 'SD', 'Western'); +INSERT INTO roads.us_states VALUES (43, 'Tennessee', 'TN', 'Western'); +INSERT INTO roads.us_states VALUES (44, 'Texas', 'TX', 'Western'); +INSERT INTO roads.us_states VALUES (45, 'Utah', 'UT', 'Western'); +INSERT INTO roads.us_states VALUES (46, 'Vermont', 'VT', 'Eastern'); +INSERT INTO roads.us_states VALUES (47, 'Virginia', 'VA', 'Eastern'); +INSERT INTO roads.us_states VALUES (48, 'Washington', 'WA', 'Western'); +INSERT INTO roads.us_states VALUES (49, 'West Virginia', 'WV', 'Southern'); +INSERT INTO roads.us_states VALUES (50, 'Wisconsin', 'WI', 'Western'); +INSERT INTO roads.us_states VALUES (51, 'Wyoming', 'WY', 'Western'); + +CREATE TABLE roads.municipality_municipality_type ( + municipality_id character varying(20) NOT NULL, + municipality_type_id character varying(1) NOT NULL +); +INSERT INTO roads.municipality_municipality_type VALUES ('New York', 'A'); +INSERT INTO roads.municipality_municipality_type VALUES ('Los Angeles', 'B'); +INSERT INTO roads.municipality_municipality_type VALUES ('Chicago', 'B'); +INSERT INTO roads.municipality_municipality_type VALUES ('Houston', 'A'); +INSERT INTO roads.municipality_municipality_type VALUES ('Phoenix', 'A'); +INSERT INTO roads.municipality_municipality_type VALUES ('Philadelphia', 'B'); +INSERT INTO roads.municipality_municipality_type VALUES ('San Antonio', 'A'); +INSERT INTO roads.municipality_municipality_type VALUES ('San Diego', 'B'); +INSERT INTO roads.municipality_municipality_type VALUES ('Dallas', 'A'); +INSERT INTO roads.municipality_municipality_type VALUES ('San Jose', 'B'); + +CREATE TABLE roads.municipality_type ( + municipality_type_id character varying(1) NOT NULL, + municipality_type_desc text +); +INSERT INTO roads.municipality_type VALUES ('A', 'Primary'); +INSERT INTO roads.municipality_type VALUES ('A', 'Secondary'); diff --git a/datajunction-query/docker/postgres_init.sql b/datajunction-query/docker/postgres_init.sql new file mode 100644 index 000000000..f582f68b5 --- /dev/null +++ b/datajunction-query/docker/postgres_init.sql @@ -0,0 +1,63 @@ +-- +-- Basic example +-- +CREATE SCHEMA IF NOT EXISTS basic; + +-- +-- basic.dim_users +-- +CREATE TABLE IF NOT EXISTS basic.dim_users ( + id integer PRIMARY KEY, + full_name text, + age integer, + country text, + gender text, + preferred_language text +); + +INSERT INTO basic.dim_users (id, full_name, age, country, gender, preferred_language) + VALUES + (1, 'Alice One', 10, 'Argentina', 'female', 'Spanish'), + (2, 'Bob Two', 15, 'Brazil', 'male', 'Portuguese'), + (3, 'Charlie Three', 20, 'Chile', 'non-binary', 'Spanish'), + (4, 'Denise Four', 25, 'Denmark', 'female', 'Danish'), + (5, 'Ernie Five', 27, 'Equator', 'male', 'Spanish'), + (6, 'Fabian Six', 29, 'France', 'non-binary', 'French') +; + +-- +-- basic.comments +-- +CREATE TABLE IF NOT EXISTS basic.comments ( + id integer PRIMARY KEY, + user_id integer, + "timestamp" timestamp with time zone, + "text" text, + CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES basic.dim_users (id) +); + +INSERT INTO basic.comments (id, user_id, "timestamp", "text") + VALUES + (1, 1, '2021-01-01 01:00:00', 'Hola!'), + (2, 2, '2021-01-01 02:00:00', 'Oi, tudo bom?'), + (3, 3, '2021-01-01 03:00:00', 'Que pasa?'), + (4, 4, '2021-01-01 04:00:00', 'Også mig'), + (5, 5, '2021-01-01 05:00:00', 'Bueno'), + (6, 6, '2021-01-01 06:00:00', 'Bonjour!'), + (7, 2, '2021-01-01 07:00:00', 'Prazer em conhecer'), + (8, 3, '2021-01-01 08:00:00', 'Si, si'), + (9, 4, '2021-01-01 09:00:00', 'Hej'), + (10, 5, '2021-01-01 10:00:00', 'Por supuesto'), + (11, 6, '2021-01-01 11:00:00', 'Oui, oui'), + (12, 3, '2021-01-01 12:00:00', 'Como no?'), + (13, 4, '2021-01-01 13:00:00', 'Farvel'), + (14, 5, '2021-01-01 14:00:00', 'Hola, amigo!'), + (15, 6, '2021-01-01 15:00:00', 'Très bien'), + (16, 4, '2021-01-01 16:00:00', 'Dejligt at møde dig'), + (17, 5, '2021-01-01 17:00:00', 'Dale!'), + (18, 6, '2021-01-01 18:00:00', 'Bien sûr!'), + (19, 5, '2021-01-01 19:00:00', 'Hasta luego!'), + (20, 6, '2021-01-01 20:00:00', 'À toute à l'' heure ! '), + (21, 6, '2021-01-01 21:00:00', 'Peut être'), + (22, 6, '2021-01-01 00:00:00', 'Cześć!') +; diff --git a/datajunction-query/docker/spark.roads.sql b/datajunction-query/docker/spark.roads.sql new file mode 100644 index 000000000..9f6e6ad06 --- /dev/null +++ b/datajunction-query/docker/spark.roads.sql @@ -0,0 +1,267 @@ +CREATE DATABASE roads; + +CREATE TABLE roads.repair_type ( + repair_type_id int, + repair_type_name string, + contractor_id string +); +INSERT INTO roads.repair_type VALUES +(1, 'Asphalt Overlay', 'Asphalt overlays restore roads to a smooth condition. This resurfacing uses the deteriorating asphalt as a base for which the new layer is added on top of, instead of tearing up the worsening one.'), +(2, 'Patching', 'Patching is the process of filling potholes or excavated areas in the asphalt pavement. Quick repair of potholes or other pavement disintegration helps control further deterioration and expensive repair of the pavement. Without timely patching, water can enter the sub-grade and cause larger and more serious pavement failures.'), +(3, 'Reshaping', 'This is necessary when a road surface it too damaged to be smoothed. Using a grader blade and scarifying if necessary, you rework the gravel sub-base to eliminate large potholes and rebuild a flattened crown.'), +(4, 'Slab Replacement', 'This refers to replacing sections of paved roads. It is a good option for when slabs are chipped, cracked, or uneven, and mitigates the need to replace the entire road when just a small section is damaged.'), +(5, 'Smoothing', 'This is when you lightly rework the gravel of a road without digging in too far to the sub-base. Typically, a motor grader is used in this operation with an attached blade. Smoothing is done when the road has minor damage or is just worn down a bit from use.'), +(6, 'Reconstruction', 'When roads have deteriorated to a point that it is no longer cost-effective to maintain, the entire street or road needs to be rebuilt. Typically, this work is done in phases to limit traffic restrictions. As part of reconstruction, the street may be realigned to improve safety or operations, grading may be changed to improve storm water flow, underground utilities may be added, upgraded or relocated, traffic signals and street lights may be relocated, and street trees and pedestrian ramps may be added.'); + +CREATE TABLE roads.municipality ( + municipality_id string, + contact_name string, + contact_title string, + local_region string, + state_id int +); +INSERT INTO roads.municipality VALUES +('New York', 'Alexander Wilkinson', 'Assistant City Clerk', 'Manhattan', 33), +('Los Angeles', 'Hugh Moser', 'Administrative Assistant', 'Santa Monica',5 ), +('Chicago', 'Phillip Bradshaw', 'Director of Community Engagement', 'West Ridge', 14), +('Houston', 'Leo Ackerman', 'Municipal Roads Specialist', 'The Woodlands', 44), +('Phoenix', 'Jessie Paul', 'Director of Finance and Administration', 'Old Town Scottsdale', 3), +('Philadelphia', 'Willie Chaney', 'Municipal Manager', 'Center City', 39), +('San Antonio', 'Chester Lyon', 'Treasurer', 'Alamo Heights', 44), +('San Diego', 'Ralph Helms', 'Senior Electrical Project Manager', 'Del Mar', 5), +('Dallas', 'Virgil Craft', 'Assistant Assessor (Town/Municipality)', 'Deep Ellum', 44), +('San Jose', 'Charles Carney', 'Municipal Accounting Manager', 'Santana Row', 5); + +CREATE TABLE roads.hard_hats ( + hard_hat_id int, + last_name string, + first_name string, + title string, + birth_date date, + hire_date date, + address string, + city string, + state string, + postal_code string, + country string, + manager int, + contractor_id int +); +INSERT INTO roads.hard_hats VALUES +(1, 'Brian', 'Perkins', 'Construction Laborer', cast('1978-11-28' as date), cast('2009-02-06' as date), '4 Jennings Ave.', 'Jersey City', 'NJ', '37421', 'USA', 9, 1), +(2, 'Nicholas', 'Massey', 'Carpenter', cast('1993-02-19' as date), cast('2003-04-14' as date), '9373 Southampton Street', 'Middletown', 'CT', '27292', 'USA', 9, 1), +(3, 'Cathy', 'Best', 'Framer', cast('1994-08-30' as date), cast('1990-07-02' as date), '4 Hillside Street', 'Billerica', 'MA', '13440', 'USA', 9, 2), +(4, 'Melanie', 'Stafford', 'Construction Manager', cast('1966-03-19' as date), cast('2003-02-02' as date), '77 Studebaker Lane', 'Southampton', 'PA', '71730', 'USA', 9, 2), +(5, 'Donna', 'Riley', 'Pre-construction Manager', cast('1983-03-14' as date), cast('2012-01-13' as date), '82 Taylor Drive', 'Southgate', 'MI', '33125', 'USA', 9, 4), +(6, 'Alfred', 'Clarke', 'Construction Superintendent', cast('1979-01-12' as date), cast('2013-10-17' as date), '7729 Catherine Street', 'Powder Springs', 'GA', '42001', 'USA', 9, 2), +(7, 'William', 'Boone', 'Construction Laborer', cast('1970-02-28' as date), cast('2013-01-02' as date), '1 Border St.', 'Niagara Falls', 'NY', '14304', 'USA', 9, 4), +(8, 'Luka', 'Henderson', 'Construction Laborer', cast('1988-12-09' as date), cast('2013-03-05' as date), '794 S. Chapel Ave.', 'Phoenix', 'AZ', '85021', 'USA', 9, 1), +(9, 'Patrick', 'Ziegler', 'Construction Laborer', cast('1976-11-27' as date), cast('2020-11-15' as date), '321 Gainsway Circle', 'Muskogee', 'OK', '74403', 'USA', 9, 3); + +CREATE TABLE roads.hard_hat_state ( + hard_hat_id int, + state_id int +); +INSERT INTO roads.hard_hat_state VALUES +(1, 2), +(2, 32), +(3, 28), +(4, 12), +(5, 5), +(6, 3), +(7, 16), +(8, 32), +(9, 41); + +CREATE TABLE roads.repair_order_details ( + repair_order_id int, + repair_type_id int, + price real NOT NULL, + quantity int, + discount real NOT NULL +); +INSERT INTO roads.repair_order_details VALUES +(10001, 1, 63708, 1, 0.05), +(10002, 4, 67253, 1, 0.05), +(10003, 2, 66808, 1, 0.05), +(10004, 4, 18497, 1, 0.05), +(10005, 7, 76463, 1, 0.05), +(10006, 4, 87858, 1, 0.05), +(10007, 1, 63918, 1, 0.05), +(10008, 6, 21083, 1, 0.05), +(10009, 3, 74555, 1, 0.05), +(10010, 5, 27222, 1, 0.05), +(10011, 5, 73600, 1, 0.05), +(10012, 3, 54901, 1, 0.01), +(10013, 5, 51594, 1, 0.01), +(10014, 1, 65114, 1, 0.01), +(10015, 1, 48919, 1, 0.01), +(10016, 3, 70418, 1, 0.01), +(10017, 1, 29684, 1, 0.01), +(10018, 2, 62928, 1, 0.01), +(10019, 2, 97916, 1, 0.01), +(10020, 5, 44120, 1, 0.01), +(10021, 1, 53374, 1, 0.01), +(10022, 2, 87289, 1, 0.01), +(10023, 2, 92366, 1, 0.01), +(10024, 2, 47857, 1, 0.01), +(10025, 1, 68745, 1, 0.01); + +CREATE TABLE roads.repair_orders ( + repair_order_id int, + municipality_id string, + hard_hat_id int, + order_date date, + required_date date, + dispatched_date date, + dispatcher_id int +); +INSERT INTO roads.repair_orders VALUES +(10001, 'New York', 1, cast('2007-07-04' as date), cast('2009-07-18' as date), cast('2007-12-01' as date), 3), +(10002, 'New York', 3, cast('2007-07-05' as date), cast('2009-08-28' as date), cast('2007-12-01' as date), 1), +(10003, 'New York', 5, cast('2007-07-08' as date), cast('2009-08-12' as date), cast('2007-12-01' as date), 2), +(10004, 'Dallas', 1, cast('2007-07-08' as date), cast('2009-08-01' as date), cast('2007-12-01' as date), 1), +(10005, 'San Antonio', 8, cast('2007-07-09' as date), cast('2009-08-01' as date), cast('2007-12-01' as date), 2), +(10006, 'New York', 3, cast('2007-07-10' as date), cast('2009-08-01' as date), cast('2007-12-01' as date), 2), +(10007, 'Philadelphia', 4, cast('2007-04-21' as date), cast('2009-08-08' as date), cast('2007-12-01' as date), 2), +(10008, 'Philadelphia', 5, cast('2007-04-22' as date), cast('2009-08-09' as date), cast('2007-12-01' as date), 3), +(10009, 'Philadelphia', 3, cast('2007-04-25' as date), cast('2009-08-12' as date), cast('2007-12-01' as date), 2), +(10010, 'Philadelphia', 4, cast('2007-04-26' as date), cast('2009-08-13' as date), cast('2007-12-01' as date), 3), +(10011, 'Philadelphia', 4, cast('2007-04-27' as date), cast('2009-08-14' as date), cast('2007-12-01' as date), 1), +(10012, 'Philadelphia', 8, cast('2007-04-28' as date), cast('2009-08-15' as date), cast('2007-12-01' as date), 3), +(10013, 'Philadelphia', 4, cast('2007-04-29' as date), cast('2009-08-16' as date), cast('2007-12-01' as date), 1), +(10014, 'Philadelphia', 6, cast('2007-04-29' as date), cast('2009-08-16' as date), cast('2007-12-01' as date), 2), +(10015, 'Philadelphia', 2, cast('2007-04-12' as date), cast('2009-08-19' as date), cast('2007-12-01' as date), 3), +(10016, 'Philadelphia', 9, cast('2007-04-13' as date), cast('2009-08-20' as date), cast('2007-12-01' as date), 3), +(10017, 'Philadelphia', 2, cast('2007-04-14' as date), cast('2009-08-21' as date), cast('2007-12-01' as date), 3), +(10018, 'Philadelphia', 6, cast('2007-04-15' as date), cast('2009-08-22' as date), cast('2007-12-01' as date), 1), +(10019, 'Philadelphia', 5, cast('2007-05-16' as date), cast('2009-09-06' as date), cast('2007-12-01' as date), 3), +(10020, 'Philadelphia', 1, cast('2007-05-19' as date), cast('2009-08-26' as date), cast('2007-12-01' as date), 1), +(10021, 'Philadelphia', 7, cast('2007-05-10' as date), cast('2009-08-27' as date), cast('2007-12-01' as date), 3), +(10022, 'Philadelphia', 5, cast('2007-05-11' as date), cast('2009-08-14' as date), cast('2007-12-01' as date), 1), +(10023, 'Philadelphia', 1, cast('2007-05-11' as date), cast('2009-08-29' as date), cast('2007-12-01' as date), 1), +(10024, 'Philadelphia', 5, cast('2007-05-11' as date), cast('2009-08-29' as date), cast('2007-12-01' as date), 2), +(10025, 'Philadelphia', 6, cast('2007-05-12' as date), cast('2009-08-30' as date), cast('2007-12-01' as date), 2); + +CREATE TABLE roads.dispatchers ( + dispatcher_id int, + company_name string, + phone string +); +INSERT INTO roads.dispatchers VALUES +(1, 'Pothole Pete', '(111) 111-1111'), +(2, 'Asphalts R Us', '(222) 222-2222'), +(3, 'Federal Roads Group', '(333) 333-3333'), +(4, 'Local Patchers', '1-800-888-8888'), +(5, 'Gravel INC', '1-800-000-0000'), +(6, 'DJ Developers', '1-111-111-1111'); + +CREATE TABLE roads.contractors ( + contractor_id int, + company_name string, + contact_name string, + contact_title string, + address string, + city string, + state string, + postal_code string, + country string, + phone string +); +INSERT INTO roads.contractors VALUES +(1, 'You Need Em We Find Em', 'Max Potter', 'Assistant Director', '4 Plumb Branch Lane', 'Goshen', 'IN', '46526', 'USA', '(111) 111-1111'), +(2, 'Call Forwarding', 'Sylvester English', 'Administrator', '9650 Mill Lane', 'Raeford', 'NC', '28376', 'USA', '(222) 222-2222'), +(3, 'The Connect', 'Paul Raymond', 'Administrator', '7587 Myrtle Ave.', 'Chaska', 'MN', '55318', 'USA', '(333) 333-3333'); + +CREATE TABLE roads.us_region ( + us_region_id int, + us_region_description string +); +INSERT INTO roads.us_region VALUES +(1, 'Eastern'), +(2, 'Western'), +(3, 'Northern'), +(4, 'Southern'); + +CREATE TABLE roads.us_states ( + state_id int, + state_name string, + state_abbr string, + state_region string +); +INSERT INTO roads.us_states VALUES +(1, 'Alabama', 'AL', 'Southern'), +(2, 'Alaska', 'AK', 'Northern'), +(3, 'Arizona', 'AZ', 'Western'), +(4, 'Arkansas', 'AR', 'Southern'), +(5, 'California', 'CA', 'Western'), +(6, 'Colorado', 'CO', 'Western'), +(7, 'Connecticut', 'CT', 'Eastern'), +(8, 'Delaware', 'DE', 'Eastern'), +(9, 'District of Columbia', 'DC', 'Eastern'), +(10, 'Florida', 'FL', 'Southern'), +(11, 'Georgia', 'GA', 'Southern'), +(12, 'Hawaii', 'HI', 'Western'), +(13, 'Idaho', 'ID', 'Western'), +(14, 'Illinois', 'IL', 'Western'), +(15, 'Indiana', 'IN', 'Western'), +(16, 'Iowa', 'IO', 'Western'), +(17, 'Kansas', 'KS', 'Western'), +(18, 'Kentucky', 'KY', 'Southern'), +(19, 'Louisiana', 'LA', 'Southern'), +(20, 'Maine', 'ME', 'Northern'), +(21, 'Maryland', 'MD', 'Eastern'), +(22, 'Massachusetts', 'MA', 'Northern'), +(23, 'Michigan', 'MI', 'Northern'), +(24, 'Minnesota', 'MN', 'Northern'), +(25, 'Mississippi', 'MS', 'Southern'), +(26, 'Missouri', 'MO', 'Southern'), +(27, 'Montana', 'MT', 'Western'), +(28, 'Nebraska', 'NE', 'Western'), +(29, 'Nevada', 'NV', 'Western'), +(30, 'New Hampshire', 'NH', 'Eastern'), +(31, 'New Jersey', 'NJ', 'Eastern'), +(32, 'New Mexico', 'NM', 'Western'), +(33, 'New York', 'NY', 'Eastern'), +(34, 'North Carolina', 'NC', 'Eastern'), +(35, 'North Dakota', 'ND', 'Western'), +(36, 'Ohio', 'OH', 'Western'), +(37, 'Oklahoma', 'OK', 'Western'), +(38, 'Oregon', 'OR', 'Western'), +(39, 'Pennsylvania', 'PA', 'Eastern'), +(40, 'Rhode Island', 'RI', 'Eastern'), +(41, 'South Carolina', 'SC', 'Eastern'), +(42, 'South Dakota', 'SD', 'Western'), +(43, 'Tennessee', 'TN', 'Western'), +(44, 'Texas', 'TX', 'Western'), +(45, 'Utah', 'UT', 'Western'), +(46, 'Vermont', 'VT', 'Eastern'), +(47, 'Virginia', 'VA', 'Eastern'), +(48, 'Washington', 'WA', 'Western'), +(49, 'West Virginia', 'WV', 'Southern'), +(50, 'Wisconsin', 'WI', 'Western'), +(51, 'Wyoming', 'WY', 'Western'); + +CREATE TABLE roads.municipality_municipality_type ( + municipality_id string, + municipality_type_id string +); +INSERT INTO roads.municipality_municipality_type VALUES +('New York', 'A'), +('Los Angeles', 'B'), +('Chicago', 'B'), +('Houston', 'A'), +('Phoenix', 'A'), +('Philadelphia', 'B'), +('San Antonio', 'A'), +('San Diego', 'B'), +('Dallas', 'A'), +('San Jose', 'B'); + +CREATE TABLE roads.municipality_type ( + municipality_type_id string, + municipality_type_desc string +); +INSERT INTO roads.municipality_type VALUES +('A', 'Primary'), +('A', 'Secondary'); diff --git a/datajunction-query/docker/spark_load_roads.py b/datajunction-query/docker/spark_load_roads.py new file mode 100644 index 000000000..0e1fa1be8 --- /dev/null +++ b/datajunction-query/docker/spark_load_roads.py @@ -0,0 +1,19 @@ +""" +Load the roads database into a local spark warehouse +""" +from pyspark.sql import SparkSession # pylint: disable=import-error + +print("Starting spark session...") +spark = ( + SparkSession.builder.master("local[*]") + .appName("djqs-load-roads") + .enableHiveSupport() + .getOrCreate() +) +with open("/code/docker/spark.roads.sql", encoding="UTF-8") as sql_file: + queries = sql_file.read() + for query in queries.split(";"): + if query.strip(): + print("Submitting query...") + spark.sql(query) + print("Query completed...") diff --git a/examples/docker/wait-for b/datajunction-query/docker/wait-for similarity index 100% rename from examples/docker/wait-for rename to datajunction-query/docker/wait-for diff --git a/datajunction-query/openapi.json b/datajunction-query/openapi.json new file mode 100644 index 000000000..d86e92266 --- /dev/null +++ b/datajunction-query/openapi.json @@ -0,0 +1,912 @@ +{ + "openapi": "3.0.2", + "info": { + "title": "DJ server", + "description": "A DataJunction metrics repository", + "license": { + "name": "MIT License", + "url": "https://mit-license.org/" + }, + "version": "0.0.post1.dev1+g2c5d4fa" + }, + "paths": { + "/databases/": { + "get": { + "summary": "Read Databases", + "description": "List the available databases.", + "operationId": "read_databases_databases__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read Databases Databases Get", + "type": "array", + "items": { + "$ref": "#/components/schemas/Database" + } + } + } + } + } + } + } + }, + "/queries/": { + "post": { + "summary": "Submit Query", + "description": "Run or schedule a query.\n\nThis endpoint is different from others in that it accepts both JSON and msgpack, and\ncan also return JSON or msgpack, depending on HTTP headers.", + "operationId": "submit_query_queries__post", + "parameters": [ + { + "required": false, + "schema": { + "title": "Accept", + "type": "string" + }, + "name": "accept", + "in": "header" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "title": "QueryCreate", + "required": [ + "database_id", + "submitted_query" + ], + "type": "object", + "properties": { + "database_id": { + "title": "Database Id", + "type": "integer" + }, + "catalog": { + "title": "Catalog", + "type": "string" + }, + "schema": { + "title": "Schema", + "type": "string" + }, + "submitted_query": { + "title": "Submitted Query", + "type": "string" + } + }, + "description": "Model for submitted queries." + } + }, + "application/msgpack": { + "schema": { + "title": "QueryCreate", + "required": [ + "database_id", + "submitted_query" + ], + "type": "object", + "properties": { + "database_id": { + "title": "Database Id", + "type": "integer" + }, + "catalog": { + "title": "Catalog", + "type": "string" + }, + "schema": { + "title": "Schema", + "type": "string" + }, + "submitted_query": { + "title": "Submitted Query", + "type": "string" + } + }, + "description": "Model for submitted queries." + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Return results as JSON or msgpack", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/QueryWithResults" + } + }, + "application/msgpack": {} + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/queries/{query_id}/": { + "get": { + "summary": "Read Query", + "description": "Fetch information about a query.\n\nFor paginated queries we move the data from the results backend to the cache for a\nshort period, anticipating additional requests.", + "operationId": "read_query_queries__query_id___get", + "parameters": [ + { + "required": true, + "schema": { + "title": "Query Id", + "type": "string", + "format": "uuid" + }, + "name": "query_id", + "in": "path" + }, + { + "required": false, + "schema": { + "title": "Limit", + "type": "integer", + "default": 0 + }, + "name": "limit", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "Offset", + "type": "integer", + "default": 0 + }, + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/QueryWithResults" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/metrics/": { + "get": { + "summary": "Read Metrics", + "description": "List all available metrics.", + "operationId": "read_metrics_metrics__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read Metrics Metrics Get", + "type": "array", + "items": { + "$ref": "#/components/schemas/Metric" + } + } + } + } + } + } + } + }, + "/metrics/{node_id}/": { + "get": { + "summary": "Read Metric", + "description": "Return a metric by ID.", + "operationId": "read_metric_metrics__node_id___get", + "parameters": [ + { + "required": true, + "schema": { + "title": "Node Id", + "type": "integer" + }, + "name": "node_id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Metric" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/metrics/{node_id}/data/": { + "get": { + "summary": "Read Metrics Data", + "description": "Return data for a metric.", + "operationId": "read_metrics_data_metrics__node_id__data__get", + "parameters": [ + { + "required": true, + "schema": { + "title": "Node Id", + "type": "integer" + }, + "name": "node_id", + "in": "path" + }, + { + "required": false, + "schema": { + "title": "Database Id", + "type": "integer" + }, + "name": "database_id", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "D", + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "name": "d", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "F", + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "name": "f", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/QueryWithResults" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/metrics/{node_id}/sql/": { + "get": { + "summary": "Read Metrics Sql", + "description": "Return SQL for a metric.\n\nA database can be optionally specified. If no database is specified the optimal one\nwill be used.", + "operationId": "read_metrics_sql_metrics__node_id__sql__get", + "parameters": [ + { + "required": true, + "schema": { + "title": "Node Id", + "type": "integer" + }, + "name": "node_id", + "in": "path" + }, + { + "required": false, + "schema": { + "title": "Database Id", + "type": "integer" + }, + "name": "database_id", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "D", + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "name": "d", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "F", + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "name": "f", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TranslatedSQL" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/nodes/": { + "get": { + "summary": "Read Nodes", + "description": "List the available nodes.", + "operationId": "read_nodes_nodes__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read Nodes Nodes Get", + "type": "array", + "items": { + "$ref": "#/components/schemas/NodeMetadata" + } + } + } + } + } + } + } + }, + "/graphql": { + "get": { + "summary": "Handle Http Get", + "operationId": "handle_http_get_graphql_get", + "responses": { + "200": { + "description": "The GraphiQL integrated development environment.", + "content": { + "application/json": { + "schema": {} + } + } + }, + "404": { + "description": "Not found if GraphiQL is not enabled." + } + } + }, + "post": { + "summary": "Handle Http Post", + "operationId": "handle_http_post_graphql_post", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + } + }, + "components": { + "schemas": { + "ColumnMetadata": { + "title": "ColumnMetadata", + "required": [ + "name", + "type" + ], + "type": "object", + "properties": { + "name": { + "title": "Name", + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/ColumnType" + } + }, + "description": "A simple model for column metadata." + }, + "ColumnType": { + "title": "ColumnType", + "enum": [ + "BYTES", + "STR", + "FLOAT", + "INT", + "DECIMAL", + "BOOL", + "DATETIME", + "DATE", + "TIME", + "TIMEDELTA", + "LIST", + "DICT" + ], + "type": "string", + "description": "\n Types for columns.\n\n These represent the values from the ``python_type`` attribute in SQLAlchemy columns.\n " + }, + "Database": { + "title": "Database", + "required": [ + "name", + "URI" + ], + "type": "object", + "properties": { + "id": { + "title": "Id", + "type": "integer" + }, + "uuid": { + "title": "Uuid", + "type": "string", + "format": "uuid" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "title": "Description", + "type": "string", + "default": "" + }, + "URI": { + "title": "Uri", + "type": "string" + }, + "extra_params": { + "title": "Extra Params", + "type": "object", + "default": {} + }, + "read_only": { + "title": "Read Only", + "type": "boolean", + "default": true + }, + "async_": { + "title": "Async ", + "type": "boolean", + "default": false + }, + "cost": { + "title": "Cost", + "type": "number", + "default": 1.0 + }, + "created_at": { + "title": "Created At", + "type": "string", + "format": "date-time" + }, + "updated_at": { + "title": "Updated At", + "type": "string", + "format": "date-time" + } + }, + "description": "A database.\n\nA simple example:\n\n name: druid\n description: An Apache Druid database\n URI: druid://localhost:8082/druid/v2/sql/\n read-only: true\n async_: false\n cost: 1.0" + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + } + } + } + }, + "Metric": { + "title": "Metric", + "required": [ + "id", + "name", + "created_at", + "updated_at", + "query", + "dimensions" + ], + "type": "object", + "properties": { + "id": { + "title": "Id", + "type": "integer" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "title": "Description", + "type": "string", + "default": "" + }, + "created_at": { + "title": "Created At", + "type": "string", + "format": "date-time" + }, + "updated_at": { + "title": "Updated At", + "type": "string", + "format": "date-time" + }, + "query": { + "title": "Query", + "type": "string" + }, + "dimensions": { + "title": "Dimensions", + "type": "array", + "items": { + "type": "string" + } + } + }, + "description": "Class for a metric." + }, + "NodeMetadata": { + "title": "NodeMetadata", + "required": [ + "id", + "name", + "created_at", + "updated_at", + "type", + "columns" + ], + "type": "object", + "properties": { + "id": { + "title": "Id", + "type": "integer" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "title": "Description", + "type": "string", + "default": "" + }, + "created_at": { + "title": "Created At", + "type": "string", + "format": "date-time" + }, + "updated_at": { + "title": "Updated At", + "type": "string", + "format": "date-time" + }, + "type": { + "$ref": "#/components/schemas/NodeType" + }, + "query": { + "title": "Query", + "type": "string" + }, + "columns": { + "title": "Columns", + "type": "array", + "items": { + "$ref": "#/components/schemas/SimpleColumn" + } + } + }, + "description": "A node with information about columns and if it is a metric." + }, + "NodeType": { + "title": "NodeType", + "enum": [ + "source", + "transform", + "metric", + "dimension" + ], + "type": "string", + "description": "\n Node type.\n\n A node can have 4 types, currently:\n\n 1. SOURCE nodes are root nodes in the DAG, and point to tables or views in a DB.\n 2. TRANSFORM nodes are SQL transformations, reading from SOURCE/TRANSFORM nodes.\n 3. METRIC nodes are leaves in the DAG, and have a single aggregation query.\n 4. DIMENSION nodes are special SOURCE nodes that can be auto-joined with METRICS.\n " + }, + "QueryResults": { + "title": "QueryResults", + "type": "array", + "items": { + "$ref": "#/components/schemas/StatementResults" + }, + "description": "Results for a given query." + }, + "QueryState": { + "title": "QueryState", + "enum": [ + "UNKNOWN", + "ACCEPTED", + "SCHEDULED", + "RUNNING", + "FINISHED", + "CANCELED", + "FAILED" + ], + "type": "string", + "description": "\n Different states of a query.\n " + }, + "QueryWithResults": { + "title": "QueryWithResults", + "required": [ + "database_id", + "id", + "submitted_query", + "results", + "errors" + ], + "type": "object", + "properties": { + "database_id": { + "title": "Database Id", + "type": "integer" + }, + "catalog": { + "title": "Catalog", + "type": "string" + }, + "schema": { + "title": "Schema", + "type": "string" + }, + "id": { + "title": "Id", + "type": "string", + "format": "uuid" + }, + "submitted_query": { + "title": "Submitted Query", + "type": "string" + }, + "executed_query": { + "title": "Executed Query", + "type": "string" + }, + "scheduled": { + "title": "Scheduled", + "type": "string", + "format": "date-time" + }, + "started": { + "title": "Started", + "type": "string", + "format": "date-time" + }, + "finished": { + "title": "Finished", + "type": "string", + "format": "date-time" + }, + "state": { + "allOf": [ + { + "$ref": "#/components/schemas/QueryState" + } + ], + "default": "UNKNOWN" + }, + "progress": { + "title": "Progress", + "type": "number", + "default": 0.0 + }, + "results": { + "$ref": "#/components/schemas/QueryResults" + }, + "next": { + "title": "Next", + "maxLength": 65536, + "minLength": 1, + "type": "string", + "format": "uri" + }, + "previous": { + "title": "Previous", + "maxLength": 65536, + "minLength": 1, + "type": "string", + "format": "uri" + }, + "errors": { + "title": "Errors", + "type": "array", + "items": { + "type": "string" + } + } + }, + "description": "Model for query with results." + }, + "SimpleColumn": { + "title": "SimpleColumn", + "required": [ + "name", + "type" + ], + "type": "object", + "properties": { + "name": { + "title": "Name", + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/ColumnType" + } + }, + "description": "A simplified column schema, without ID or dimensions." + }, + "StatementResults": { + "title": "StatementResults", + "required": [ + "sql", + "columns", + "rows" + ], + "type": "object", + "properties": { + "sql": { + "title": "Sql", + "type": "string" + }, + "columns": { + "title": "Columns", + "type": "array", + "items": { + "$ref": "#/components/schemas/ColumnMetadata" + } + }, + "rows": { + "title": "Rows", + "type": "array", + "items": { + "type": "array", + "items": {} + } + }, + "row_count": { + "title": "Row Count", + "type": "integer", + "default": 0 + } + }, + "description": "Results for a given statement.\n\nThis contains the SQL, column names and types, and rows" + }, + "TranslatedSQL": { + "title": "TranslatedSQL", + "required": [ + "database_id", + "sql" + ], + "type": "object", + "properties": { + "database_id": { + "title": "Database Id", + "type": "integer" + }, + "sql": { + "title": "Sql", + "type": "string" + } + }, + "description": "Class for SQL generated from a given metric." + }, + "ValidationError": { + "title": "ValidationError", + "required": [ + "loc", + "msg", + "type" + ], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "type": "string" + } + }, + "msg": { + "title": "Message", + "type": "string" + }, + "type": { + "title": "Error Type", + "type": "string" + } + } + } + } + } +} \ No newline at end of file diff --git a/datajunction-query/pdm.lock b/datajunction-query/pdm.lock new file mode 100644 index 000000000..f61403d92 --- /dev/null +++ b/datajunction-query/pdm.lock @@ -0,0 +1,1775 @@ +# This file is @generated by PDM. +# It is not intended for manual editing. + +[[package]] +name = "accept-types" +version = "0.4.1" +summary = "Determine the best content to send in an HTTP response" + +[[package]] +name = "alembic" +version = "1.11.1" +requires_python = ">=3.7" +summary = "A database migration tool for SQLAlchemy." +dependencies = [ + "Mako", + "SQLAlchemy>=1.3.0", + "importlib-metadata; python_version < \"3.9\"", + "importlib-resources; python_version < \"3.9\"", + "typing-extensions>=4", +] + +[[package]] +name = "anyio" +version = "3.7.1" +requires_python = ">=3.7" +summary = "High level compatibility layer for multiple asynchronous event loop implementations" +dependencies = [ + "exceptiongroup; python_version < \"3.11\"", + "idna>=2.8", + "sniffio>=1.1", +] + +[[package]] +name = "astroid" +version = "2.15.6" +requires_python = ">=3.7.2" +summary = "An abstract syntax tree for Python with inference support." +dependencies = [ + "lazy-object-proxy>=1.4.0", + "typing-extensions>=4.0.0; python_version < \"3.11\"", + "wrapt<2,>=1.11; python_version < \"3.11\"", + "wrapt<2,>=1.14; python_version >= \"3.11\"", +] + +[[package]] +name = "build" +version = "0.10.0" +requires_python = ">= 3.7" +summary = "A simple, correct Python build frontend" +dependencies = [ + "colorama; os_name == \"nt\"", + "packaging>=19.0", + "pyproject-hooks", + "tomli>=1.1.0; python_version < \"3.11\"", +] + +[[package]] +name = "cachelib" +version = "0.10.2" +requires_python = ">=3.7" +summary = "A collection of cache libraries in the same API interface." + +[[package]] +name = "certifi" +version = "2023.5.7" +requires_python = ">=3.6" +summary = "Python package for providing Mozilla's CA Bundle." + +[[package]] +name = "cfgv" +version = "3.3.1" +requires_python = ">=3.6.1" +summary = "Validate configuration and produce human readable error messages." + +[[package]] +name = "charset-normalizer" +version = "3.2.0" +requires_python = ">=3.7.0" +summary = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." + +[[package]] +name = "click" +version = "8.1.5" +requires_python = ">=3.7" +summary = "Composable command line interface toolkit" +dependencies = [ + "colorama; platform_system == \"Windows\"", +] + +[[package]] +name = "codespell" +version = "2.2.5" +requires_python = ">=3.7" +summary = "Codespell" + +[[package]] +name = "colorama" +version = "0.4.6" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +summary = "Cross-platform colored terminal text." + +[[package]] +name = "coverage" +version = "7.2.7" +requires_python = ">=3.7" +summary = "Code coverage measurement for Python" + +[[package]] +name = "coverage" +version = "7.2.7" +extras = ["toml"] +requires_python = ">=3.7" +summary = "Code coverage measurement for Python" +dependencies = [ + "coverage==7.2.7", + "tomli; python_full_version <= \"3.11.0a6\"", +] + +[[package]] +name = "dill" +version = "0.3.6" +requires_python = ">=3.7" +summary = "serialize all of python" + +[[package]] +name = "distlib" +version = "0.3.6" +summary = "Distribution utilities" + +[[package]] +name = "duckdb" +version = "0.8.1" +summary = "DuckDB embedded database" + +[[package]] +name = "duckdb-engine" +version = "0.9.1" +requires_python = ">=3.7" +summary = "SQLAlchemy driver for duckdb" +dependencies = [ + "duckdb>=0.4.0", + "sqlalchemy>=1.3.22", +] + +[[package]] +name = "exceptiongroup" +version = "1.1.2" +requires_python = ">=3.7" +summary = "Backport of PEP 654 (exception groups)" + +[[package]] +name = "fastapi" +version = "0.100.0" +requires_python = ">=3.7" +summary = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +dependencies = [ + "pydantic!=1.8,!=1.8.1,!=2.0.0,!=2.0.1,<3.0.0,>=1.7.4", + "starlette<0.28.0,>=0.27.0", + "typing-extensions>=4.5.0", +] + +[[package]] +name = "filelock" +version = "3.12.2" +requires_python = ">=3.7" +summary = "A platform independent file lock." + +[[package]] +name = "freezegun" +version = "1.2.2" +requires_python = ">=3.6" +summary = "Let your Python tests travel through time" +dependencies = [ + "python-dateutil>=2.7", +] + +[[package]] +name = "greenlet" +version = "2.0.2" +requires_python = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" +summary = "Lightweight in-process concurrent programming" + +[[package]] +name = "h11" +version = "0.14.0" +requires_python = ">=3.7" +summary = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" + +[[package]] +name = "httpcore" +version = "0.17.3" +requires_python = ">=3.7" +summary = "A minimal low-level HTTP client." +dependencies = [ + "anyio<5.0,>=3.0", + "certifi", + "h11<0.15,>=0.13", + "sniffio==1.*", +] + +[[package]] +name = "httptools" +version = "0.6.0" +requires_python = ">=3.5.0" +summary = "A collection of framework independent HTTP protocol utils." + +[[package]] +name = "httpx" +version = "0.24.1" +requires_python = ">=3.7" +summary = "The next generation HTTP client." +dependencies = [ + "certifi", + "httpcore<0.18.0,>=0.15.0", + "idna", + "sniffio", +] + +[[package]] +name = "identify" +version = "2.5.24" +requires_python = ">=3.7" +summary = "File identification library for Python" + +[[package]] +name = "idna" +version = "3.4" +requires_python = ">=3.5" +summary = "Internationalized Domain Names in Applications (IDNA)" + +[[package]] +name = "importlib-metadata" +version = "6.8.0" +requires_python = ">=3.8" +summary = "Read metadata from Python packages" +dependencies = [ + "zipp>=0.5", +] + +[[package]] +name = "importlib-resources" +version = "6.0.0" +requires_python = ">=3.8" +summary = "Read resources from Python packages" +dependencies = [ + "zipp>=3.1.0; python_version < \"3.10\"", +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +requires_python = ">=3.7" +summary = "brain-dead simple config-ini parsing" + +[[package]] +name = "isort" +version = "5.12.0" +requires_python = ">=3.8.0" +summary = "A Python utility / library to sort Python imports." + +[[package]] +name = "lazy-object-proxy" +version = "1.9.0" +requires_python = ">=3.7" +summary = "A fast and thorough lazy object proxy." + +[[package]] +name = "mako" +version = "1.2.4" +requires_python = ">=3.7" +summary = "A super-fast templating language that borrows the best ideas from the existing templating languages." +dependencies = [ + "MarkupSafe>=0.9.2", +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +requires_python = ">=3.8" +summary = "Python port of markdown-it. Markdown parsing, done right!" +dependencies = [ + "mdurl~=0.1", +] + +[[package]] +name = "markupsafe" +version = "2.1.3" +requires_python = ">=3.7" +summary = "Safely add untrusted strings to HTML/XML markup." + +[[package]] +name = "mccabe" +version = "0.7.0" +requires_python = ">=3.6" +summary = "McCabe checker, plugin for flake8" + +[[package]] +name = "mdurl" +version = "0.1.2" +requires_python = ">=3.7" +summary = "Markdown URL utilities" + +[[package]] +name = "msgpack" +version = "1.0.5" +summary = "MessagePack serializer" + +[[package]] +name = "nodeenv" +version = "1.8.0" +requires_python = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +summary = "Node.js virtual environment builder" +dependencies = [ + "setuptools", +] + +[[package]] +name = "packaging" +version = "23.1" +requires_python = ">=3.7" +summary = "Core utilities for Python packages" + +[[package]] +name = "pip" +version = "23.2" +requires_python = ">=3.7" +summary = "The PyPA recommended tool for installing Python packages." + +[[package]] +name = "pip-tools" +version = "7.0.0" +requires_python = ">=3.8" +summary = "pip-tools keeps your pinned dependencies fresh." +dependencies = [ + "build", + "click>=8", + "pip>=22.2", + "setuptools", + "tomli; python_version < \"3.11\"", + "wheel", +] + +[[package]] +name = "platformdirs" +version = "3.9.1" +requires_python = ">=3.7" +summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." + +[[package]] +name = "pluggy" +version = "1.2.0" +requires_python = ">=3.7" +summary = "plugin and hook calling mechanisms for python" + +[[package]] +name = "pre-commit" +version = "3.3.3" +requires_python = ">=3.8" +summary = "A framework for managing and maintaining multi-language pre-commit hooks." +dependencies = [ + "cfgv>=2.0.0", + "identify>=1.0.0", + "nodeenv>=0.11.1", + "pyyaml>=5.1", + "virtualenv>=20.10.0", +] + +[[package]] +name = "py4j" +version = "0.10.9.5" +summary = "Enables Python programs to dynamically access arbitrary Java objects" + +[[package]] +name = "pydantic" +version = "1.10.11" +requires_python = ">=3.7" +summary = "Data validation and settings management using python type hints" +dependencies = [ + "typing-extensions>=4.2.0", +] + +[[package]] +name = "pydruid" +version = "0.6.5" +summary = "A Python connector for Druid." +dependencies = [ + "requests", +] + +[[package]] +name = "pyfakefs" +version = "5.2.3" +requires_python = ">=3.7" +summary = "pyfakefs implements a fake file system that mocks the Python file system modules." + +[[package]] +name = "pygments" +version = "2.15.1" +requires_python = ">=3.7" +summary = "Pygments is a syntax highlighting package written in Python." + +[[package]] +name = "pylint" +version = "2.17.4" +requires_python = ">=3.7.2" +summary = "python code static checker" +dependencies = [ + "astroid<=2.17.0-dev0,>=2.15.4", + "colorama>=0.4.5; sys_platform == \"win32\"", + "dill>=0.2; python_version < \"3.11\"", + "dill>=0.3.6; python_version >= \"3.11\"", + "isort<6,>=4.2.5", + "mccabe<0.8,>=0.6", + "platformdirs>=2.2.0", + "tomli>=1.1.0; python_version < \"3.11\"", + "tomlkit>=0.10.1", + "typing-extensions>=3.10.0; python_version < \"3.10\"", +] + +[[package]] +name = "pyproject-hooks" +version = "1.0.0" +requires_python = ">=3.7" +summary = "Wrappers to call pyproject.toml-based build backend hooks." +dependencies = [ + "tomli>=1.1.0; python_version < \"3.11\"", +] + +[[package]] +name = "pyspark" +version = "3.3.2" +requires_python = ">=3.7" +summary = "Apache Spark Python API" +dependencies = [ + "py4j==0.10.9.5", +] + +[[package]] +name = "pytest" +version = "7.4.0" +requires_python = ">=3.7" +summary = "pytest: simple powerful testing with Python" +dependencies = [ + "colorama; sys_platform == \"win32\"", + "exceptiongroup>=1.0.0rc8; python_version < \"3.11\"", + "iniconfig", + "packaging", + "pluggy<2.0,>=0.12", + "tomli>=1.0.0; python_version < \"3.11\"", +] + +[[package]] +name = "pytest-asyncio" +version = "0.15.1" +requires_python = ">= 3.6" +summary = "Pytest support for asyncio." +dependencies = [ + "pytest>=5.4.0", +] + +[[package]] +name = "pytest-cov" +version = "4.1.0" +requires_python = ">=3.7" +summary = "Pytest plugin for measuring coverage." +dependencies = [ + "coverage[toml]>=5.2.1", + "pytest>=4.6", +] + +[[package]] +name = "pytest-integration" +version = "0.2.2" +requires_python = ">=3.6" +summary = "Organizing pytests by integration or not" + +[[package]] +name = "pytest-mock" +version = "3.11.1" +requires_python = ">=3.7" +summary = "Thin-wrapper around the mock package for easier use with pytest" +dependencies = [ + "pytest>=5.0", +] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +summary = "Extensions to the standard Python datetime module" +dependencies = [ + "six>=1.5", +] + +[[package]] +name = "python-dotenv" +version = "0.19.2" +requires_python = ">=3.5" +summary = "Read key-value pairs from a .env file and set them as environment variables" + +[[package]] +name = "pyyaml" +version = "6.0" +requires_python = ">=3.6" +summary = "YAML parser and emitter for Python" + +[[package]] +name = "requests" +version = "2.29.0" +requires_python = ">=3.7" +summary = "Python HTTP for Humans." +dependencies = [ + "certifi>=2017.4.17", + "charset-normalizer<4,>=2", + "idna<4,>=2.5", + "urllib3<1.27,>=1.21.1", +] + +[[package]] +name = "requests-mock" +version = "1.11.0" +summary = "Mock out responses from the requests package" +dependencies = [ + "requests<3,>=2.3", + "six", +] + +[[package]] +name = "rich" +version = "13.4.2" +requires_python = ">=3.7.0" +summary = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +dependencies = [ + "markdown-it-py>=2.2.0", + "pygments<3.0.0,>=2.13.0", + "typing-extensions<5.0,>=4.0.0; python_version < \"3.9\"", +] + +[[package]] +name = "setuptools" +version = "68.0.0" +requires_python = ">=3.7" +summary = "Easily download, build, install, upgrade, and uninstall Python packages" + +[[package]] +name = "six" +version = "1.16.0" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +summary = "Python 2 and 3 compatibility utilities" + +[[package]] +name = "sniffio" +version = "1.3.0" +requires_python = ">=3.7" +summary = "Sniff out which async library your code is running under" + +[[package]] +name = "sqlalchemy" +version = "1.4.41" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +summary = "Database Abstraction Library" +dependencies = [ + "greenlet!=0.4.17; python_version >= \"3\" and (platform_machine == \"aarch64\" or (platform_machine == \"ppc64le\" or (platform_machine == \"x86_64\" or (platform_machine == \"amd64\" or (platform_machine == \"AMD64\" or (platform_machine == \"win32\" or platform_machine == \"WIN32\"))))))", +] + +[[package]] +name = "sqlalchemy-utils" +version = "0.41.1" +requires_python = ">=3.6" +summary = "Various utility functions for SQLAlchemy." +dependencies = [ + "SQLAlchemy>=1.3", +] + +[[package]] +name = "sqlalchemy2-stubs" +version = "0.0.2a35" +requires_python = ">=3.6" +summary = "Typing Stubs for SQLAlchemy 1.4" +dependencies = [ + "typing-extensions>=3.7.4", +] + +[[package]] +name = "sqlmodel" +version = "0.0.8" +requires_python = ">=3.6.1,<4.0.0" +summary = "SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness." +dependencies = [ + "SQLAlchemy<=1.4.41,>=1.4.17", + "pydantic<2.0.0,>=1.8.2", + "sqlalchemy2-stubs", +] + +[[package]] +name = "sqlparse" +version = "0.4.4" +requires_python = ">=3.5" +summary = "A non-validating SQL parser." + +[[package]] +name = "starlette" +version = "0.27.0" +requires_python = ">=3.7" +summary = "The little ASGI library that shines." +dependencies = [ + "anyio<5,>=3.4.0", + "typing-extensions>=3.10.0; python_version < \"3.10\"", +] + +[[package]] +name = "tomli" +version = "2.0.1" +requires_python = ">=3.7" +summary = "A lil' TOML parser" + +[[package]] +name = "tomlkit" +version = "0.11.8" +requires_python = ">=3.7" +summary = "Style preserving TOML library" + +[[package]] +name = "typing-extensions" +version = "4.7.1" +requires_python = ">=3.7" +summary = "Backported and Experimental Type Hints for Python 3.7+" + +[[package]] +name = "urllib3" +version = "1.26.16" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +summary = "HTTP library with thread-safe connection pooling, file post, and more." + +[[package]] +name = "uvicorn" +version = "0.23.0" +requires_python = ">=3.8" +summary = "The lightning-fast ASGI server." +dependencies = [ + "click>=7.0", + "h11>=0.8", +] + +[[package]] +name = "uvicorn" +version = "0.23.0" +extras = ["standard"] +requires_python = ">=3.8" +summary = "The lightning-fast ASGI server." +dependencies = [ + "colorama>=0.4; sys_platform == \"win32\"", + "httptools>=0.5.0", + "python-dotenv>=0.13", + "pyyaml>=5.1", + "uvicorn==0.23.0", + "uvloop!=0.15.0,!=0.15.1,>=0.14.0; sys_platform != \"win32\" and (sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\")", + "watchfiles>=0.13", + "websockets>=10.4", +] + +[[package]] +name = "uvloop" +version = "0.17.0" +requires_python = ">=3.7" +summary = "Fast implementation of asyncio event loop on top of libuv" + +[[package]] +name = "virtualenv" +version = "20.24.0" +requires_python = ">=3.7" +summary = "Virtual Python Environment builder" +dependencies = [ + "distlib<1,>=0.3.6", + "filelock<4,>=3.12", + "platformdirs<4,>=3.5.1", +] + +[[package]] +name = "watchfiles" +version = "0.19.0" +requires_python = ">=3.7" +summary = "Simple, modern and high performance file watching and code reload in python." +dependencies = [ + "anyio>=3.0.0", +] + +[[package]] +name = "websockets" +version = "11.0.3" +requires_python = ">=3.7" +summary = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" + +[[package]] +name = "wheel" +version = "0.40.0" +requires_python = ">=3.7" +summary = "A built-package format for Python" + +[[package]] +name = "wrapt" +version = "1.15.0" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +summary = "Module for decorators, wrappers and monkey patching." + +[[package]] +name = "zipp" +version = "3.16.2" +requires_python = ">=3.8" +summary = "Backport of pathlib-compatible object wrapper for zip files" + +[metadata] +lock_version = "4.2" +cross_platform = true +groups = ["default", "test", "uvicorn"] +content_hash = "sha256:3a2789a368b5dc797d1f79753e75192e106a665765f3646b89c8522cbd66c657" + +[metadata.files] +"accept-types 0.4.1" = [ + {url = "https://files.pythonhosted.org/packages/08/24/559c0f1959134a0e7615a0ece082734209b1942365ec479cbe3bcca9098b/accept_types-0.4.1-py3-none-any.whl", hash = "sha256:c87feccdffb66b02f9343ff387d7fd5c451ccb2e1221fbd37ea0cedef5cf290f"}, + {url = "https://files.pythonhosted.org/packages/a3/84/6f51d94019411892c9f7fa9d461d4cef06beb35d54cd9944ea19728c4d45/accept-types-0.4.1.tar.gz", hash = "sha256:fb27099716d8f0360408c8ca86d69dbfed44455834b70d1506250abe521b535a"}, +] +"alembic 1.11.1" = [ + {url = "https://files.pythonhosted.org/packages/11/00/46a4f66ad54c661350a1cd5daae4b4ab2232486c55635ee12ff12958b03f/alembic-1.11.1-py3-none-any.whl", hash = "sha256:dc871798a601fab38332e38d6ddb38d5e734f60034baeb8e2db5b642fccd8ab8"}, + {url = "https://files.pythonhosted.org/packages/c6/e3/3d9b95470606b93bd6e6d5c899ed9d0049dfa10246ecca25b18c2c708cdf/alembic-1.11.1.tar.gz", hash = "sha256:6a810a6b012c88b33458fceb869aef09ac75d6ace5291915ba7fae44de372c01"}, +] +"anyio 3.7.1" = [ + {url = "https://files.pythonhosted.org/packages/19/24/44299477fe7dcc9cb58d0a57d5a7588d6af2ff403fdd2d47a246c91a3246/anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"}, + {url = "https://files.pythonhosted.org/packages/28/99/2dfd53fd55ce9838e6ff2d4dac20ce58263798bd1a0dbe18b3a9af3fcfce/anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"}, +] +"astroid 2.15.6" = [ + {url = "https://files.pythonhosted.org/packages/86/6f/22a8153395e193369fd43ca9d54123e74360115df4f10902516313eb9f10/astroid-2.15.6-py3-none-any.whl", hash = "sha256:389656ca57b6108f939cf5d2f9a2a825a3be50ba9d589670f393236e0a03b91c"}, + {url = "https://files.pythonhosted.org/packages/d5/05/2c2ef3504e32a22b224614d45c590abb5be417cfed47ee63bf6089c3ae56/astroid-2.15.6.tar.gz", hash = "sha256:903f024859b7c7687d7a7f3a3f73b17301f8e42dfd9cc9df9d4418172d3e2dbd"}, +] +"build 0.10.0" = [ + {url = "https://files.pythonhosted.org/packages/58/91/17b00d5fac63d3dca605f1b8269ba3c65e98059e1fd99d00283e42a454f0/build-0.10.0-py3-none-any.whl", hash = "sha256:af266720050a66c893a6096a2f410989eeac74ff9a68ba194b3f6473e8e26171"}, + {url = "https://files.pythonhosted.org/packages/de/1c/fb62f81952f0e74c3fbf411261d1adbdd2d615c89a24b42d0fe44eb4bcf3/build-0.10.0.tar.gz", hash = "sha256:d5b71264afdb5951d6704482aac78de887c80691c52b88a9ad195983ca2c9269"}, +] +"cachelib 0.10.2" = [ + {url = "https://files.pythonhosted.org/packages/70/0b/e7647e072ff60997d69517072145ef56898278afda7deff7cc6858b1541f/cachelib-0.10.2.tar.gz", hash = "sha256:593faeee62a7c037d50fc835617a01b887503f972fb52b188ae7e50e9cb69740"}, + {url = "https://files.pythonhosted.org/packages/8c/83/449fb201dd5a6536bb022eb50ddc58da9e3d5cdf674c12df2f6dd46bd52f/cachelib-0.10.2-py3-none-any.whl", hash = "sha256:42d49f2fad9310dd946d7be73d46776bcd4d5fde4f49ad210cfdd447fbdfc346"}, +] +"certifi 2023.5.7" = [ + {url = "https://files.pythonhosted.org/packages/93/71/752f7a4dd4c20d6b12341ed1732368546bc0ca9866139fe812f6009d9ac7/certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"}, + {url = "https://files.pythonhosted.org/packages/9d/19/59961b522e6757f0c9097e4493fa906031b95b3ebe9360b2c3083561a6b4/certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"}, +] +"cfgv 3.3.1" = [ + {url = "https://files.pythonhosted.org/packages/6d/82/0a0ebd35bae9981dea55c06f8e6aaf44a49171ad798795c72c6f64cba4c2/cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, + {url = "https://files.pythonhosted.org/packages/c4/bf/d0d622b660d414a47dc7f0d303791a627663f554345b21250e39e7acb48b/cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, +] +"charset-normalizer 3.2.0" = [ + {url = "https://files.pythonhosted.org/packages/08/f7/3f36bb1d0d74846155c7e3bf1477004c41243bb510f9082e785809787735/charset_normalizer-3.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c"}, + {url = "https://files.pythonhosted.org/packages/09/79/1b7af063e7c57a51aab7f2aaccd79bb8a694dfae668e8aa79b0b045b17bc/charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c"}, + {url = "https://files.pythonhosted.org/packages/0d/dd/e598cc4e4052aa0779d4c6d5e9840d21ed238834944ccfbc6b33f792c426/charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c"}, + {url = "https://files.pythonhosted.org/packages/0f/16/8d50877a7215d31f024245a0acbda9e484dd70a21794f3109a6d8eaeba99/charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2"}, + {url = "https://files.pythonhosted.org/packages/13/de/10c14aa51375b90ed62232935e6c8997756178e6972c7695cdf0500a60ad/charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"}, + {url = "https://files.pythonhosted.org/packages/16/36/72dcb89fbd0ff89c556ed4a2cc79fc1b262dcc95e9082d8a5911744dadc9/charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592"}, + {url = "https://files.pythonhosted.org/packages/19/9f/552f15cb1dade9332d6f0208fa3e6c21bb3eecf1c89862413ed8a3c75900/charset_normalizer-3.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959"}, + {url = "https://files.pythonhosted.org/packages/1b/2c/7376d101efdec15e61e9861890cf107c6ce3cceba89eb87cc416ee0528cd/charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e"}, + {url = "https://files.pythonhosted.org/packages/23/59/8011a01cd8b904d08d86b4a49f407e713d20ee34155300dc698892a29f8b/charset_normalizer-3.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252"}, + {url = "https://files.pythonhosted.org/packages/27/19/49de2049561eca73233ba0ed7a843c184d364ef3b8886969a48d6793c830/charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23"}, + {url = "https://files.pythonhosted.org/packages/28/ec/cda85baa366071c48593774eb59a5031793dd974fa26f4982829e971df6b/charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918"}, + {url = "https://files.pythonhosted.org/packages/2a/53/cf0a48de1bdcf6ff6e1c9a023f5f523dfe303e4024f216feac64b6eb7f67/charset-normalizer-3.2.0.tar.gz", hash = "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace"}, + {url = "https://files.pythonhosted.org/packages/2e/29/dc806e009ddb357371458de3e93cfde78ea6e5c995df008fb6b048769457/charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a"}, + {url = "https://files.pythonhosted.org/packages/2e/56/faee2b51d73e9675b4766366d925f17c253797e5839c28e1c720ec9dfbfc/charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f"}, + {url = "https://files.pythonhosted.org/packages/31/e9/ae16eca3cf24a15ebfb1e36d755c884a91d61ed40de5e612de6555827729/charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3"}, + {url = "https://files.pythonhosted.org/packages/3d/91/47454b64516f83c5affdcdb0398bff540185d2c37b687410d67507006624/charset_normalizer-3.2.0-cp38-cp38-win32.whl", hash = "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706"}, + {url = "https://files.pythonhosted.org/packages/45/60/1b2113fe172ac66ac4d210034e937ebe0be30bcae9a7a4d2ae5ad3c018b3/charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8"}, + {url = "https://files.pythonhosted.org/packages/47/03/2cde6c5fba0115e8726272aabfca33b9d84d377cc11c4bab092fa9617d7a/charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5"}, + {url = "https://files.pythonhosted.org/packages/47/71/2ce8dca3e8cf1f65c36b6317cf68382bb259966e3a208da6e5550029ab79/charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10"}, + {url = "https://files.pythonhosted.org/packages/49/60/87a026215ed77184c413ebb85bafa6c0a998bdc0d1e03b894fa326f2b0f9/charset_normalizer-3.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037"}, + {url = "https://files.pythonhosted.org/packages/4a/46/a22af93e707f0d3c3865a2c21b4363c778239f5a6405aadd220992ac3058/charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952"}, + {url = "https://files.pythonhosted.org/packages/4d/ce/8ce85a7d61bbfb5e49094040642f1558b3cf6cf2ad91bbb3616a967dea38/charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7"}, + {url = "https://files.pythonhosted.org/packages/59/8e/62651b09599938e5e6d068ea723fd22d3f8c14d773c3c11c58e5e7d1eab7/charset_normalizer-3.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3"}, + {url = "https://files.pythonhosted.org/packages/5a/60/eeb158f11b0dee921d3e44bf37971271060b234ee60b14fa16ccc1947cbe/charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c"}, + {url = "https://files.pythonhosted.org/packages/5c/f2/f3faa20684729d3910af2ee142e30432c7a46a817eadeeab87366ed87bbb/charset_normalizer-3.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea"}, + {url = "https://files.pythonhosted.org/packages/5d/28/f69dac79bf3986a52bc2f7dc561360c2c9c88cb0270738d86ee5a3d8a0ba/charset_normalizer-3.2.0-cp37-cp37m-win32.whl", hash = "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1"}, + {url = "https://files.pythonhosted.org/packages/5f/52/e8ca03368aeecdd5c0057bd1f8ef189796d232b152e3de4244bb5a72d135/charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3"}, + {url = "https://files.pythonhosted.org/packages/63/f9/14ffa4b88c1b42837dfa488b0943b7bd7f54f5b63135bf97e5001f6957e7/charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4"}, + {url = "https://files.pythonhosted.org/packages/6b/b2/9d0c8fe83572a37bd66150399e289d8e96d62eca359ffa67c021b4120887/charset_normalizer-3.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e"}, + {url = "https://files.pythonhosted.org/packages/6b/b7/f042568ee89c378b457f73fda1642fd3b795df79c285520e4ec8a74c8b09/charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3"}, + {url = "https://files.pythonhosted.org/packages/6f/14/8e317fa69483a2823ea358a77e243c37f23f536a7add1b605460269593b5/charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa"}, + {url = "https://files.pythonhosted.org/packages/79/55/9aef5046a1765acacf28f80994f5a964ab4f43ab75208b1265191a11004b/charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329"}, + {url = "https://files.pythonhosted.org/packages/7b/c6/7f75892d87d7afcf8ed909f3e74de1bc61abd9d77cd9aab1f449430856c5/charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f"}, + {url = "https://files.pythonhosted.org/packages/80/75/eadff07a61d5602b6b19859d464bc0983654ae79114ef8aa15797b02271c/charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4"}, + {url = "https://files.pythonhosted.org/packages/81/a0/96317ce912b512b7998434eae5e24b28bcc5f1680ad85348e31e1ca56332/charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed"}, + {url = "https://files.pythonhosted.org/packages/85/52/77ab28e0eb07f12a02732c55abfc3be481bd46c91d5ade76a8904dfb59a4/charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22"}, + {url = "https://files.pythonhosted.org/packages/89/f5/88e9dd454756fea555198ddbe6fa40d6408ec4f10ad4f0a911e0b7e471e4/charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449"}, + {url = "https://files.pythonhosted.org/packages/8b/b4/e6da7d4c044852d7a08ba945868eaefa32e8c43665e746f420ef14bdb130/charset_normalizer-3.2.0-cp39-cp39-win32.whl", hash = "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9"}, + {url = "https://files.pythonhosted.org/packages/8b/c4/62b920ec8f4ec7b55cd29db894ced9a649214fd506295ac19fb786fe3c6f/charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f"}, + {url = "https://files.pythonhosted.org/packages/8e/a2/77cf1f042a4697822070fd5f3f5f58fd0e3ee798d040e3863eac43e3a2e5/charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09"}, + {url = "https://files.pythonhosted.org/packages/91/6e/db0e545302bf93b6dbbdc496dd192c7f8e8c3bb1584acba069256d8b51d4/charset_normalizer-3.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489"}, + {url = "https://files.pythonhosted.org/packages/91/e6/8fa919fc84a106e9b04109de62bdf8526899e2754a64da66e1cd50ac1faa/charset_normalizer-3.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac"}, + {url = "https://files.pythonhosted.org/packages/94/fc/53e12f67fff7a127fe2998de3469a9856c6c7cf67f18dc5f417df3e5e60f/charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd"}, + {url = "https://files.pythonhosted.org/packages/95/d2/6f25fddfbe31448ceea236e03b70d2bbd647d4bc9148bf9665307794c4f2/charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd"}, + {url = "https://files.pythonhosted.org/packages/95/d3/ed29b2d14ec9044a223dcf7c439fa550ef9c6d06c9372cd332374d990559/charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299"}, + {url = "https://files.pythonhosted.org/packages/95/ee/8bb03c3518a228dc5956d1b4f46d8258639ff118881fba456b72b06561cf/charset_normalizer-3.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346"}, + {url = "https://files.pythonhosted.org/packages/97/f6/0bae7bdfb07ca42bf5e3e37dbd0cce02d87dd6e87ea85dff43106dfc1f48/charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669"}, + {url = "https://files.pythonhosted.org/packages/99/23/7262c6a7c8a8c2ec783886166a432985915f67277bc44020d181e5c04584/charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6"}, + {url = "https://files.pythonhosted.org/packages/9c/71/bf12b8e0d6e1d84ed29c3e16ea1efc47ae96487bde823130d12139c434a0/charset_normalizer-3.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149"}, + {url = "https://files.pythonhosted.org/packages/9c/74/10a518cd27c2c595768f70ddbd7d05c9acb01b26033f79433105ccc73308/charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982"}, + {url = "https://files.pythonhosted.org/packages/a1/5c/c4ae954751f285c6170c3ef4de04492f88ddb29d218fefbdcbd9fb32ba5c/charset_normalizer-3.2.0-cp311-cp311-win32.whl", hash = "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1"}, + {url = "https://files.pythonhosted.org/packages/a4/65/057bf29660aae6ade0816457f8db4e749e5c0bfa2366eb5f67db9912fa4c/charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad"}, + {url = "https://files.pythonhosted.org/packages/ad/0d/9aa61083c35dc21e75a97c0ee53619daf0e5b4fd3b8b4d8bb5e7e56ed302/charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94"}, + {url = "https://files.pythonhosted.org/packages/af/3d/57e7e401f8db6dd0c56e366d69dc7366173fc549bcd533dea15f2a805000/charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a"}, + {url = "https://files.pythonhosted.org/packages/af/6f/b9b1613a5b672004f08ef3c02242b07406ff36164725ff15207737601de5/charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710"}, + {url = "https://files.pythonhosted.org/packages/b6/2a/03e909cad170b0df5ce8b731fecbc872b7b922a1d38da441b5062a89e53f/charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2"}, + {url = "https://files.pythonhosted.org/packages/bc/85/ef25d4ba14c7653c3020a1c6e1a7413e6791ef36a0ac177efa605fc2c737/charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6"}, + {url = "https://files.pythonhosted.org/packages/bf/a0/188f223c7d8b924fb9b554b9d27e0e7506fd5bf9cfb6dbacb2dfd5832b53/charset_normalizer-3.2.0-py3-none-any.whl", hash = "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6"}, + {url = "https://files.pythonhosted.org/packages/c1/92/4e30c977d2dc49ca7f84a053ccefd86097a9d1a220f3e1d1f9932561a992/charset_normalizer-3.2.0-cp310-cp310-win32.whl", hash = "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96"}, + {url = "https://files.pythonhosted.org/packages/cb/dd/dce14328e6abe0f475e606131298b4c8f628abd62a4e6f27fdfa496b9efe/charset_normalizer-3.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80"}, + {url = "https://files.pythonhosted.org/packages/cb/e7/5e43745003bf1f90668c7be23fc5952b3a2b9c2558f16749411c18039b36/charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a"}, + {url = "https://files.pythonhosted.org/packages/cb/f9/a652e1b495345000bb7f0e2a960a82ca941db55cb6de158d542918f8b52b/charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2"}, + {url = "https://files.pythonhosted.org/packages/d3/d8/50a33f82bdf25e71222a55cef146310e3e9fe7d5790be5281d715c012eae/charset_normalizer-3.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858"}, + {url = "https://files.pythonhosted.org/packages/e8/74/077cb06aed5d41118a5803e842943311032ab2fb94cf523be620c5be9911/charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029"}, + {url = "https://files.pythonhosted.org/packages/e8/ad/ac491a1cf960ec5873c1b0e4fd4b90b66bfed4a1063933612f2da8189eb8/charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d"}, + {url = "https://files.pythonhosted.org/packages/ec/a7/96835706283d63fefbbbb4f119d52f195af00fc747e67cc54397c56312c8/charset_normalizer-3.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9"}, + {url = "https://files.pythonhosted.org/packages/ed/21/03b4a3533b7a845ee31ed4542ca06debdcf7f12c099ae3dd6773c275b0df/charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c"}, + {url = "https://files.pythonhosted.org/packages/ee/ff/997d61ca61efe90662181f494c8e9fdac14e32de26cc6cb7c7a3fe96c862/charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a"}, + {url = "https://files.pythonhosted.org/packages/f0/24/7e6c604d80a8eb4378cb075647e65b7905f06645243b43c79fe4b7487ed7/charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623"}, + {url = "https://files.pythonhosted.org/packages/f1/f2/ef1479e741a7ed166b8253987071b2cf2d2b727fc8fa081520e3f7c97e44/charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a"}, + {url = "https://files.pythonhosted.org/packages/f2/e8/d9651a0afd4ee792207b24bd1d438ed750f1c0f29df62bd73d24ded428f9/charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020"}, + {url = "https://files.pythonhosted.org/packages/f4/39/b024eb6c2a2b8136f1f48fd2f2eee22ed98fbfe3cd7ddf81dad2b8dd3c1b/charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46"}, + {url = "https://files.pythonhosted.org/packages/f5/50/410da81fd67eb1becef9d633f6aae9f6e296f60126cfc3d19631f7919f76/charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a"}, + {url = "https://files.pythonhosted.org/packages/f9/0d/514be8597d7a96243e5467a37d337b9399cec117a513fcf9328405d911c0/charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200"}, + {url = "https://files.pythonhosted.org/packages/fd/17/0a1dba835ec37a3cc025f5c49653effb23f8cd391dea5e60a5696d639a92/charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d"}, +] +"click 8.1.5" = [ + {url = "https://files.pythonhosted.org/packages/22/b3/1da4ea0efa2e5ae410a347be614162ca08bd24a84059938aa5122d1e751b/click-8.1.5-py3-none-any.whl", hash = "sha256:e576aa487d679441d7d30abb87e1b43d24fc53bffb8758443b1a9e1cee504548"}, + {url = "https://files.pythonhosted.org/packages/7e/ad/7a6a96fab480fb2fbf52f782b2deb3abe1d2c81eca3ef68a575b5a6a4f2e/click-8.1.5.tar.gz", hash = "sha256:4be4b1af8d665c6d942909916d31a213a106800c47d0eeba73d34da3cbc11367"}, +] +"codespell 2.2.5" = [ + {url = "https://files.pythonhosted.org/packages/1d/bc/4bd1cdb7cf940ab8e8e619d3ad24c88b0257b030c6b0dd64cba3fdfa7bb8/codespell-2.2.5-py3-none-any.whl", hash = "sha256:efa037f54b73c84f7bd14ce8e853d5f822cdd6386ef0ff32e957a3919435b9ec"}, + {url = "https://files.pythonhosted.org/packages/81/30/e1b32067c551d745df2bdc9f1d510422d8a5819ca3b610b4433654cf578c/codespell-2.2.5.tar.gz", hash = "sha256:6d9faddf6eedb692bf80c9a94ec13ab4f5fb585aabae5f3750727148d7b5be56"}, +] +"colorama 0.4.6" = [ + {url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] +"coverage 7.2.7" = [ + {url = "https://files.pythonhosted.org/packages/01/24/be01e62a7bce89bcffe04729c540382caa5a06bee45ae42136c93e2499f5/coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8"}, + {url = "https://files.pythonhosted.org/packages/03/ec/6f30b4e0c96ce03b0e64aec46b4af2a8c49b70d1b5d0d69577add757b946/coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a"}, + {url = "https://files.pythonhosted.org/packages/04/d6/8cba3bf346e8b1a4fb3f084df7d8cea25a6b6c56aaca1f2e53829be17e9e/coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d"}, + {url = "https://files.pythonhosted.org/packages/04/fa/43b55101f75a5e9115259e8be70ff9279921cb6b17f04c34a5702ff9b1f7/coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97"}, + {url = "https://files.pythonhosted.org/packages/0d/31/340428c238eb506feb96d4fb5c9ea614db1149517f22cc7ab8c6035ef6d9/coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050"}, + {url = "https://files.pythonhosted.org/packages/0e/bc/7e3a31534fabb043269f14fb64e2bb2733f85d4cf39e5bbc71357c57553a/coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0"}, + {url = "https://files.pythonhosted.org/packages/15/81/b108a60bc758b448c151e5abceed027ed77a9523ecbc6b8a390938301841/coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01"}, + {url = "https://files.pythonhosted.org/packages/1f/e9/d6730247d8dec2a3dddc520ebe11e2e860f0f98cee3639e23de6cf920255/coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850"}, + {url = "https://files.pythonhosted.org/packages/22/c1/2f6c1b6f01a0996c9e067a9c780e1824351dbe17faae54388a4477e6d86f/coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959"}, + {url = "https://files.pythonhosted.org/packages/24/df/6765898d54ea20e3197a26d26bb65b084deefadd77ce7de946b9c96dfdc5/coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4"}, + {url = "https://files.pythonhosted.org/packages/28/d7/9a8de57d87f4bbc6f9a6a5ded1eaac88a89bf71369bb935dac3c0cf2893e/coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5"}, + {url = "https://files.pythonhosted.org/packages/29/8f/4fad1c2ba98104425009efd7eaa19af9a7c797e92d40cd2ec026fa1f58cb/coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495"}, + {url = "https://files.pythonhosted.org/packages/2b/86/3dbf9be43f8bf6a5ca28790a713e18902b2d884bc5fa9512823a81dff601/coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1"}, + {url = "https://files.pythonhosted.org/packages/3d/80/7060a445e1d2c9744b683dc935248613355657809d6c6b2716cdf4ca4766/coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb"}, + {url = "https://files.pythonhosted.org/packages/44/55/49f65ccdd4dfd6d5528e966b28c37caec64170c725af32ab312889d2f857/coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e"}, + {url = "https://files.pythonhosted.org/packages/45/8b/421f30467e69ac0e414214856798d4bc32da1336df745e49e49ae5c1e2a8/coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59"}, + {url = "https://files.pythonhosted.org/packages/4a/fb/78986d3022e5ccf2d4370bc43a5fef8374f092b3c21d32499dee8e30b7b6/coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e"}, + {url = "https://files.pythonhosted.org/packages/61/90/c76b9462f39897ebd8714faf21bc985b65c4e1ea6dff428ea9dc711ed0dd/coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6"}, + {url = "https://files.pythonhosted.org/packages/61/af/5964b8d7d9a5c767785644d9a5a63cacba9a9c45cc42ba06d25895ec87be/coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7"}, + {url = "https://files.pythonhosted.org/packages/66/2e/c99fe1f6396d93551aa352c75410686e726cd4ea104479b9af1af22367ce/coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250"}, + {url = "https://files.pythonhosted.org/packages/67/a2/6fa66a50e6e894286d79a3564f42bd54a9bd27049dc0a63b26d9924f0aa3/coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9"}, + {url = "https://files.pythonhosted.org/packages/67/d7/cd8fe689b5743fffac516597a1222834c42b80686b99f5b44ef43ccc2a43/coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe"}, + {url = "https://files.pythonhosted.org/packages/67/fb/b3b1d7887e1ea25a9608b0776e480e4bbc303ca95a31fd585555ec4fff5a/coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d"}, + {url = "https://files.pythonhosted.org/packages/68/5f/d2bd0f02aa3c3e0311986e625ccf97fdc511b52f4f1a063e4f37b624772f/coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a"}, + {url = "https://files.pythonhosted.org/packages/69/8c/26a95b08059db1cbb01e4b0e6d40f2e9debb628c6ca86b78f625ceaf9bab/coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511"}, + {url = "https://files.pythonhosted.org/packages/6e/ea/4a252dc77ca0605b23d477729d139915e753ee89e4c9507630e12ad64a80/coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de"}, + {url = "https://files.pythonhosted.org/packages/7a/05/084864fa4bbf8106f44fb72a56e67e0cd372d3bf9d893be818338c81af5d/coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb"}, + {url = "https://files.pythonhosted.org/packages/7b/e3/f552d5871943f747165b92a924055c5d6daa164ae659a13f9018e22f3990/coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6"}, + {url = "https://files.pythonhosted.org/packages/80/d7/67937c80b8fd4c909fdac29292bc8b35d9505312cff6bcab41c53c5b1df6/coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f"}, + {url = "https://files.pythonhosted.org/packages/88/8b/b0d9fe727acae907fa7f1c8194ccb6fe9d02e1c3e9001ecf74c741f86110/coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9"}, + {url = "https://files.pythonhosted.org/packages/88/da/495944ebf0ad246235a6bd523810d9f81981f9b81c6059ba1f56e943abe0/coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9"}, + {url = "https://files.pythonhosted.org/packages/8c/95/16eed713202406ca0a37f8ac259bbf144c9d24f9b8097a8e6ead61da2dbb/coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3"}, + {url = "https://files.pythonhosted.org/packages/8d/d6/53e999ec1bf7498ca4bc5f3b8227eb61db39068d2de5dcc359dec5601b5a/coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02"}, + {url = "https://files.pythonhosted.org/packages/8f/a8/12cc7b261f3082cc299ab61f677f7e48d93e35ca5c3c2f7241ed5525ccea/coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833"}, + {url = "https://files.pythonhosted.org/packages/91/e8/469ed808a782b9e8305a08bad8c6fa5f8e73e093bda6546c5aec68275bff/coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353"}, + {url = "https://files.pythonhosted.org/packages/94/4e/d4e46a214ae857be3d7dc5de248ba43765f60daeb1ab077cb6c1536c7fba/coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818"}, + {url = "https://files.pythonhosted.org/packages/9f/5c/d9760ac497c41f9c4841f5972d0edf05d50cad7814e86ee7d133ec4a0ac8/coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d"}, + {url = "https://files.pythonhosted.org/packages/a7/cd/3ce94ad9d407a052dc2a74fbeb1c7947f442155b28264eb467ee78dea812/coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb"}, + {url = "https://files.pythonhosted.org/packages/a9/0c/4a848ae663b47f1195abcb09a951751dd61f80b503303b9b9d768e0fd321/coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27"}, + {url = "https://files.pythonhosted.org/packages/b1/96/c12ed0dfd4ec587f3739f53eb677b9007853fd486ccb0e7d5512a27bab2e/coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562"}, + {url = "https://files.pythonhosted.org/packages/b1/d5/a8e276bc005e42114468d4fe03e0a9555786bc51cbfe0d20827a46c1565a/coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb"}, + {url = "https://files.pythonhosted.org/packages/b4/bd/1b2331e3a04f4cc9b7b332b1dd0f3a1261dfc4114f8479bebfcc2afee9e8/coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063"}, + {url = "https://files.pythonhosted.org/packages/b7/00/14b00a0748e9eda26e97be07a63cc911108844004687321ddcc213be956c/coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3"}, + {url = "https://files.pythonhosted.org/packages/b8/9d/926fce7e03dbfc653104c2d981c0fa71f0572a9ebd344d24c573bd6f7c4f/coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6"}, + {url = "https://files.pythonhosted.org/packages/ba/92/69c0722882643df4257ecc5437b83f4c17ba9e67f15dc6b77bad89b6982e/coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a"}, + {url = "https://files.pythonhosted.org/packages/bb/e9/88747b40c8fb4a783b40222510ce6d66170217eb05d7f46462c36b4fa8cc/coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2"}, + {url = "https://files.pythonhosted.org/packages/c1/49/4d487e2ad5d54ed82ac1101e467e8994c09d6123c91b2a962145f3d262c2/coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f"}, + {url = "https://files.pythonhosted.org/packages/c3/1c/6b3c9c363fb1433c79128e0d692863deb761b1b78162494abb9e5c328bc0/coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c"}, + {url = "https://files.pythonhosted.org/packages/c6/fa/529f55c9a1029c840bcc9109d5a15ff00478b7ff550a1ae361f8745f8ad5/coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f"}, + {url = "https://files.pythonhosted.org/packages/c6/fc/be19131010930a6cf271da48202c8cc1d3f971f68c02fb2d3a78247f43dc/coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5"}, + {url = "https://files.pythonhosted.org/packages/c8/e4/e6182e4697665fb594a7f4e4f27cb3a4dd00c2e3d35c5c706765de8c7866/coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9"}, + {url = "https://files.pythonhosted.org/packages/ca/0c/3dfeeb1006c44b911ee0ed915350db30325d01808525ae7cc8d57643a2ce/coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2"}, + {url = "https://files.pythonhosted.org/packages/d1/3a/67f5d18f911abf96857f6f7e4df37ca840e38179e2cc9ab6c0b9c3380f19/coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2"}, + {url = "https://files.pythonhosted.org/packages/d9/1d/cd467fceb62c371f9adb1d739c92a05d4e550246daa90412e711226bd320/coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e"}, + {url = "https://files.pythonhosted.org/packages/dd/ce/97c1dd6592c908425622fe7f31c017d11cf0421729b09101d4de75bcadc8/coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5"}, + {url = "https://files.pythonhosted.org/packages/de/a3/5a98dc9e239d0dc5f243ef5053d5b1bdcaa1dee27a691dfc12befeccf878/coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f"}, + {url = "https://files.pythonhosted.org/packages/e2/c0/73f139794c742840b9ab88e2e17fe14a3d4668a166ff95d812ac66c0829d/coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd"}, + {url = "https://files.pythonhosted.org/packages/e9/40/383305500d24122dbed73e505a4d6828f8f3356d1f68ab6d32c781754b81/coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f"}, + {url = "https://files.pythonhosted.org/packages/fe/57/e4f8ad64d84ca9e759d783a052795f62a9f9111585e46068845b1cb52c2b/coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1"}, + {url = "https://files.pythonhosted.org/packages/ff/d5/52fa1891d1802ab2e1b346d37d349cb41cdd4fd03f724ebbf94e80577687/coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4"}, +] +"dill 0.3.6" = [ + {url = "https://files.pythonhosted.org/packages/7c/e7/364a09134e1062d4d5ff69b853a56cf61c223e0afcc6906b6832bcd51ea8/dill-0.3.6.tar.gz", hash = "sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373"}, + {url = "https://files.pythonhosted.org/packages/be/e3/a84bf2e561beed15813080d693b4b27573262433fced9c1d1fea59e60553/dill-0.3.6-py3-none-any.whl", hash = "sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0"}, +] +"distlib 0.3.6" = [ + {url = "https://files.pythonhosted.org/packages/58/07/815476ae605bcc5f95c87a62b95e74a1bce0878bc7a3119bc2bf4178f175/distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, + {url = "https://files.pythonhosted.org/packages/76/cb/6bbd2b10170ed991cf64e8c8b85e01f2fb38f95d1bc77617569e0b0b26ac/distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, +] +"duckdb 0.8.1" = [ + {url = "https://files.pythonhosted.org/packages/01/13/3c82bdaca66f1dea001ce7f0b0786475bdb0b34f1c33811aa769842d2e7f/duckdb-0.8.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:67a1725c2b01f9b53571ecf3f92959b652f60156c1c48fb35798302e39b3c1a2"}, + {url = "https://files.pythonhosted.org/packages/0c/a3/4e52ef89606292b26864bcc3be3e36e1345ba4bb8a6df5b2fa36dfc01fd7/duckdb-0.8.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:297226c0dadaa07f7c5ae7cbdb9adba9567db7b16693dbd1b406b739ce0d7924"}, + {url = "https://files.pythonhosted.org/packages/13/c2/3674f4c8aa43b7ec4169209edf068984905244dd8a6f146c4086e1d18a1d/duckdb-0.8.1-cp36-cp36m-win32.whl", hash = "sha256:fad486c65ae944eae2de0d590a0a4fb91a9893df98411d66cab03359f9cba39b"}, + {url = "https://files.pythonhosted.org/packages/14/5d/3235570cdb2b5bae48d30b33ba5c18e2fa63ed7c4412c3f2a7a28c9601f0/duckdb-0.8.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:60e07a62782f88420046e30cc0e3de842d0901c4fd5b8e4d28b73826ec0c3f5e"}, + {url = "https://files.pythonhosted.org/packages/14/60/942029c50bdd5817eeda37b924ebd5ae2ea9d9bd5f94c0bf13e50015b8e1/duckdb-0.8.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:12fc13ecd5eddd28b203b9e3999040d3a7374a8f4b833b04bd26b8c5685c2635"}, + {url = "https://files.pythonhosted.org/packages/15/27/d7acabe33d635ee01971d2bd07b0c65fe2da9468cfb895254bee9be3cd44/duckdb-0.8.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f18563675977f8cbf03748efee0165b4c8ef64e0cbe48366f78e2914d82138bb"}, + {url = "https://files.pythonhosted.org/packages/19/c6/f0e69802113a3579bc2998441f80128364dc24aebeaef0609faa0cef74e9/duckdb-0.8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:3843feb79edf100800f5037c32d5d5a5474fb94b32ace66c707b96605e7c16b2"}, + {url = "https://files.pythonhosted.org/packages/22/00/a5dc58f3baaa7811bbf92818dfc5a89687c19b0be563a290825448cfb322/duckdb-0.8.1-cp37-cp37m-win32.whl", hash = "sha256:197d37e2588c5ad063e79819054eedb7550d43bf1a557d03ba8f8f67f71acc42"}, + {url = "https://files.pythonhosted.org/packages/22/eb/b9d3216579aceadd7abceb954c2522bf6648c6e720523dacec3f3e3a24b0/duckdb-0.8.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a413d5267cb41a1afe69d30dd6d4842c588256a6fed7554c7e07dad251ede095"}, + {url = "https://files.pythonhosted.org/packages/23/81/c52aa847eb3b0a4448660a102095d1eeb45f3d2ab1daf8be64aa24db7adc/duckdb-0.8.1-cp39-cp39-win32.whl", hash = "sha256:e7fe93449cd309bbc67d1bf6f6392a6118e94a9a4479ab8a80518742e855370a"}, + {url = "https://files.pythonhosted.org/packages/2e/66/f66d99eea100eaa656862f396eead4fb01b0d487aef4c7acfac993393c47/duckdb-0.8.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5ad481ee353f31250b45d64b4a104e53b21415577943aa8f84d0af266dc9af85"}, + {url = "https://files.pythonhosted.org/packages/2f/5c/935f12ce8fb1f1bf546ef939254213d361d1faf55f1f310c9d34fbdc0d4a/duckdb-0.8.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d1d1b1729993611b1892509d21c21628917625cdbe824a61ce891baadf684b32"}, + {url = "https://files.pythonhosted.org/packages/30/bb/5d60c974dd706e2a7e8b6a13aa3cca807d7f75699ad3f2425c8115728fb9/duckdb-0.8.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:01f0d4e9f7103523672bda8d3f77f440b3e0155dd3b2f24997bc0c77f8deb460"}, + {url = "https://files.pythonhosted.org/packages/3a/e0/f2504f772981afdcdca2ef2198c79867f6f0801ee21303ee55e04f4d8005/duckdb-0.8.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:31f692decb98c2d57891da27180201d9e93bb470a3051fcf413e8da65bca37a5"}, + {url = "https://files.pythonhosted.org/packages/3f/59/29ac0b2686d2ed6f533d75da14884786e9880cdbc2e541527c55d66609df/duckdb-0.8.1-cp38-cp38-win32.whl", hash = "sha256:a12bf4b18306c9cb2c9ba50520317e6cf2de861f121d6f0678505fa83468c627"}, + {url = "https://files.pythonhosted.org/packages/42/32/1c3edd959c07640f82d4612a14f22149e9e835575441b2c1e6516d588a92/duckdb-0.8.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d2c8062c3e978dbcd80d712ca3e307de8a06bd4f343aa457d7dd7294692a3842"}, + {url = "https://files.pythonhosted.org/packages/45/99/6f666787922c1c48a99aaa7682f12076bfcfcc3b9c184d3718d3b7e01ccd/duckdb-0.8.1-cp36-cp36m-win_amd64.whl", hash = "sha256:86fa4506622c52d2df93089c8e7075f1c4d0ba56f4bf27faebde8725355edf32"}, + {url = "https://files.pythonhosted.org/packages/49/0d/6dd266642a48c0baf29a14509ab90bbb246c2092a4f4c1260e999d7ce75b/duckdb-0.8.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16e179443832bea8439ae4dff93cf1e42c545144ead7a4ef5f473e373eea925a"}, + {url = "https://files.pythonhosted.org/packages/49/a5/3457dce5d5e474d7f79fde082eadcca1b30bf5a5deb48a5a42b39095fedb/duckdb-0.8.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7acedfc00d97fbdb8c3d120418c41ef3cb86ef59367f3a9a30dff24470d38680"}, + {url = "https://files.pythonhosted.org/packages/59/0a/198cdc2ade9f953683e40e2384f1634b00483d2c4d9a4910aa2d7b624b11/duckdb-0.8.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e36e35d38a9ae798fe8cf6a839e81494d5b634af89f4ec9483f4d0a313fc6bdb"}, + {url = "https://files.pythonhosted.org/packages/65/05/cd7088defc381df9ac63fa379f4f7ce7f7bc100679eabbdd6013c8dc3d9a/duckdb-0.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f13bf7ab0e56ddd2014ef762ae4ee5ea4df5a69545ce1191b8d7df8118ba3167"}, + {url = "https://files.pythonhosted.org/packages/6d/14/12d9c847a39c1d15dd3bf853d5e1d9f333dd3f97e1876750808d2e3bfe6e/duckdb-0.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:6e6583c98a7d6637e83bcadfbd86e1f183917ea539f23b6b41178f32f813a5eb"}, + {url = "https://files.pythonhosted.org/packages/6e/34/f6654864c8f9b26e4f633362f0c0ff19c4c2d9310632667f5de36ba928f6/duckdb-0.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:e4e809358b9559c00caac4233e0e2014f3f55cd753a31c4bcbbd1b55ad0d35e4"}, + {url = "https://files.pythonhosted.org/packages/75/da/81565f1cd3210442fc130389da0627e2e6b72db2dd173ab03cd277f64916/duckdb-0.8.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:81ae602f34d38d9c48dd60f94b89f28df3ef346830978441b83c5b4eae131d08"}, + {url = "https://files.pythonhosted.org/packages/76/5f/36ff597cacb235884d3c19cb182facb86a1502e08a24a114e21f1fc77551/duckdb-0.8.1-cp311-cp311-win32.whl", hash = "sha256:2d8f9cc301e8455a4f89aa1088b8a2d628f0c1f158d4cf9bc78971ed88d82eea"}, + {url = "https://files.pythonhosted.org/packages/77/a3/dc6f1bf1946fb8af6d0e6bfe4af76ef1d28a85b10d12574dc7779a840edf/duckdb-0.8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fcbe3742d77eb5add2d617d487266d825e663270ef90253366137a47eaab9448"}, + {url = "https://files.pythonhosted.org/packages/79/22/f2b229ec92d559a30bba996e85feba966fe287c6fe837b9b388ddf921c1e/duckdb-0.8.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b188b80b70d1159b17c9baaf541c1799c1ce8b2af4add179a9eed8e2616be96"}, + {url = "https://files.pythonhosted.org/packages/7a/73/edfe5e3f845eda8cf3db91af7550e9dbfc9e57ce0b2990e4c693fcfeb364/duckdb-0.8.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:12803f9f41582b68921d6b21f95ba7a51e1d8f36832b7d8006186f58c3d1b344"}, + {url = "https://files.pythonhosted.org/packages/7c/e1/ae2dac9402e7cec92c5ab04c2e658f9c8e57e4f1a7735abab0c616814ffa/duckdb-0.8.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf1ba718b7522d34399446ebd5d4b9fcac0b56b6ac07bfebf618fd190ec37c1d"}, + {url = "https://files.pythonhosted.org/packages/80/4d/140619aa73d3de8ae5ea903dba34257ce129c01e77f0db1f18ffaf9dfe71/duckdb-0.8.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5792cf777ece2c0591194006b4d3e531f720186102492872cb32ddb9363919cf"}, + {url = "https://files.pythonhosted.org/packages/83/35/acc4056703190e3203bfa7cc06bb000afaf24e8b0e63614901bebf20ae2b/duckdb-0.8.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fad7ed0d4415f633d955ac24717fa13a500012b600751d4edb050b75fb940c25"}, + {url = "https://files.pythonhosted.org/packages/84/2f/2716340caf982d555572b9aa73021ac150ddb035a077028c74fe1f1c149e/duckdb-0.8.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:47516c9299d09e9dbba097b9fb339b389313c4941da5c54109df01df0f05e78c"}, + {url = "https://files.pythonhosted.org/packages/8a/bd/f1826314fb2ed9f13f705bb42c119744d0e6b06ace5f7c46b67b9ae90975/duckdb-0.8.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23493313f88ce6e708a512daacad13e83e6d1ea0be204b175df1348f7fc78671"}, + {url = "https://files.pythonhosted.org/packages/9e/13/ab717b2924313c51e631e1fde8fb2cd5d3daa87a051fbd046d05bc7a22da/duckdb-0.8.1-cp310-cp310-win32.whl", hash = "sha256:d0953d5a2355ddc49095e7aef1392b7f59c5be5cec8cdc98b9d9dc1f01e7ce2b"}, + {url = "https://files.pythonhosted.org/packages/a1/b9/67d2e85840f8012a418c8118a79656ef62a79186b29d3a05d88848068fa4/duckdb-0.8.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3784680df59eadd683b0a4c2375d451a64470ca54bd171c01e36951962b1d332"}, + {url = "https://files.pythonhosted.org/packages/a8/dd/42a56f1589be9ff781b32a45e48108140fbaa2cb17991ff1873b819863d4/duckdb-0.8.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd82ba63b58672e46c8ec60bc9946aa4dd7b77f21c1ba09633d8847ad9eb0d7b"}, + {url = "https://files.pythonhosted.org/packages/aa/ca/cdc19bb9f24dd2213eb57c396e3e0b723fe09b519a9d5e459c87922dc040/duckdb-0.8.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae0be3f71a18cd8492d05d0fc1bc67d01d5a9457b04822d025b0fc8ee6efe32e"}, + {url = "https://files.pythonhosted.org/packages/ad/f2/644df6eb2918cb9b37b66c9b79045088a678f8ee360b371f311c084e9e04/duckdb-0.8.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1fb9bf0b6f63616c8a4b9a6a32789045e98c108df100e6bac783dc1e36073737"}, + {url = "https://files.pythonhosted.org/packages/af/af/7fc3aca20d65d9851cdf7e25b5c0ca41954c0155c4f4d907c1cfd3382411/duckdb-0.8.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7d75cfe563aaa058d3b4ccaaa371c6271e00e3070df5de72361fd161b2fe6780"}, + {url = "https://files.pythonhosted.org/packages/b2/80/207a50e4dcfa2c7e04b4bb7b4ccd2dc27a19e7ef9f74677f1de5be4e85d7/duckdb-0.8.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dbb55e7a3336f2462e5e916fc128c47fe1c03b6208d6bd413ac11ed95132aa0"}, + {url = "https://files.pythonhosted.org/packages/b3/f2/53d3be111c12313e438b9d90882bfa8b8cef01ecbc765e794de1f4b61468/duckdb-0.8.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:780a34559aaec8354e83aa4b7b31b3555f1b2cf75728bf5ce11b89a950f5cdd9"}, + {url = "https://files.pythonhosted.org/packages/c0/77/6ca11ed3e339d7fe8acd38ad75eca886ce2403f9fddf9f4f503d42d7093e/duckdb-0.8.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31a71bd8f0b0ca77c27fa89b99349ef22599ffefe1e7684ae2e1aa2904a08684"}, + {url = "https://files.pythonhosted.org/packages/cc/69/49e495d5e75d60410144efa50134713b1695a588e36dd8af72a78eef9f03/duckdb-0.8.1.tar.gz", hash = "sha256:a54d37f4abc2afc4f92314aaa56ecf215a411f40af4bffe1e86bd25e62aceee9"}, + {url = "https://files.pythonhosted.org/packages/d4/38/a5807d53a7515c9836938b9737802c616af24e345014cee075cebc83d834/duckdb-0.8.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:624c889b0f2d656794757b3cc4fc58030d5e285f5ad2ef9fba1ea34a01dab7fb"}, + {url = "https://files.pythonhosted.org/packages/d4/5f/bafa326bb094b4b9400810a3654a21ada3ec78648ac7028d24c62b09ff8e/duckdb-0.8.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6df53efd63b6fdf04657385a791a4e3c4fb94bfd5db181c4843e2c46b04fef5"}, + {url = "https://files.pythonhosted.org/packages/da/52/a8144f52e7b93968a67b8bbb163b532f53de2fc0a0e8408cbd02a05e4d52/duckdb-0.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:81d670bc6807672f038332d9bf587037aabdd741b0810de191984325ed307abd"}, + {url = "https://files.pythonhosted.org/packages/df/c6/e30f5a865d23f94d903f9731bb1fc9f113e329f0862564fd5363cd55d039/duckdb-0.8.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24568d6e48f3dbbf4a933109e323507a46b9399ed24c5d4388c4987ddc694fd0"}, + {url = "https://files.pythonhosted.org/packages/ec/31/8ff957cec74f515aacbea1dcf0f0ae9c119dee76e2f5c5a919c4f4f61e6a/duckdb-0.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:99bfe264059cdc1e318769103f656f98e819cd4e231cd76c1d1a0327f3e5cef8"}, + {url = "https://files.pythonhosted.org/packages/ec/9e/c8724532dc5b57c1336b5b45250c6a101a4d338560d16336c9f9fcc1764c/duckdb-0.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4032042d8363e55365bbca3faafc6dc336ed2aad088f10ae1a534ebc5bcc181"}, + {url = "https://files.pythonhosted.org/packages/ee/af/e3fdfa4776124e797d8303f85f3bd97e17f7a1aa1a90189b545369d267d1/duckdb-0.8.1-cp311-cp311-win_amd64.whl", hash = "sha256:07457a43605223f62d93d2a5a66b3f97731f79bbbe81fdd5b79954306122f612"}, + {url = "https://files.pythonhosted.org/packages/f8/ca/aa3b6fed6b1bf5632d172b0af8d8e4917b4013d768a121aeaad1215dd460/duckdb-0.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:538b225f361066231bc6cd66c04a5561de3eea56115a5dd773e99e5d47eb1b89"}, + {url = "https://files.pythonhosted.org/packages/fd/c1/3d4cef11f02af5c25d1d4d6dc8f15967ac3e4a2a806adce84cb695440b37/duckdb-0.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:14781d21580ee72aba1f5dcae7734674c9b6c078dd60470a08b2b420d15b996d"}, +] +"duckdb-engine 0.9.1" = [ + {url = "https://files.pythonhosted.org/packages/9b/66/055e9e10ea0605016ab188d26ebb75b529e92eddb9d35ad114e48fc16bbd/duckdb_engine-0.9.1.tar.gz", hash = "sha256:e1968620e1c1091be3ce6a5e4cf2ed185dc5cf8b4863ab408d5b625c555947ac"}, + {url = "https://files.pythonhosted.org/packages/f6/52/642514b2e12da3b929cb29bd1cd0fc99e33ddea25ac115148f43d608362b/duckdb_engine-0.9.1-py3-none-any.whl", hash = "sha256:6f909fef534c8e580e91578096296d49ac6f835fa200bf509f8f4869127c0ad3"}, +] +"exceptiongroup 1.1.2" = [ + {url = "https://files.pythonhosted.org/packages/55/09/5d2079ecab0ca483e527a1707a483562bdc17abf829d3e73f0c1a73b61c7/exceptiongroup-1.1.2.tar.gz", hash = "sha256:12c3e887d6485d16943a309616de20ae5582633e0a2eda17f4e10fd61c1e8af5"}, + {url = "https://files.pythonhosted.org/packages/fe/17/f43b7c9ccf399d72038042ee72785c305f6c6fdc6231942f8ab99d995742/exceptiongroup-1.1.2-py3-none-any.whl", hash = "sha256:e346e69d186172ca7cf029c8c1d16235aa0e04035e5750b4b95039e65204328f"}, +] +"fastapi 0.100.0" = [ + {url = "https://files.pythonhosted.org/packages/49/f5/048206823aae9b3a4a61ba6b7a1dd1de36bd4c0a0283f2efb1f1f2289c8a/fastapi-0.100.0-py3-none-any.whl", hash = "sha256:271662daf986da8fa98dc2b7c7f61c4abdfdccfb4786d79ed8b2878f172c6d5f"}, + {url = "https://files.pythonhosted.org/packages/65/e0/f9d77b3a1569e2217abf260b0b1462401736973d1c5d3d335f6f2009daa2/fastapi-0.100.0.tar.gz", hash = "sha256:acb5f941ea8215663283c10018323ba7ea737c571b67fc7e88e9469c7eb1d12e"}, +] +"filelock 3.12.2" = [ + {url = "https://files.pythonhosted.org/packages/00/0b/c506e9e44e4c4b6c89fcecda23dc115bf8e7ff7eb127e0cb9c114cbc9a15/filelock-3.12.2.tar.gz", hash = "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81"}, + {url = "https://files.pythonhosted.org/packages/00/45/ec3407adf6f6b5bf867a4462b2b0af27597a26bd3cd6e2534cb6ab029938/filelock-3.12.2-py3-none-any.whl", hash = "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec"}, +] +"freezegun 1.2.2" = [ + {url = "https://files.pythonhosted.org/packages/1d/97/002ac49ec52858538b4aa6f6831f83c2af562c17340bdf6043be695f39ac/freezegun-1.2.2.tar.gz", hash = "sha256:cd22d1ba06941384410cd967d8a99d5ae2442f57dfafeff2fda5de8dc5c05446"}, + {url = "https://files.pythonhosted.org/packages/50/cd/ba1c8319c002727ccfa03049127218d1767232a77219924d03ba170e0601/freezegun-1.2.2-py3-none-any.whl", hash = "sha256:ea1b963b993cb9ea195adbd893a48d573fda951b0da64f60883d7e988b606c9f"}, +] +"greenlet 2.0.2" = [ + {url = "https://files.pythonhosted.org/packages/07/ef/6bfa2ea34f76dea02833d66d28ae7cf4729ddab74ee93ee069c7f1d47c4f/greenlet-2.0.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:d5508f0b173e6aa47273bdc0a0b5ba055b59662ba7c7ee5119528f466585526b"}, + {url = "https://files.pythonhosted.org/packages/08/b1/0615df6393464d6819040124eb7bdff6b682f206a464b4537964819dcab4/greenlet-2.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dd11f291565a81d71dab10b7033395b7a3a5456e637cf997a6f33ebdf06f8db"}, + {url = "https://files.pythonhosted.org/packages/09/57/5fdd37939e0989a756a32d0a838409b68d1c5d348115e9c697f42ee4f87d/greenlet-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df"}, + {url = "https://files.pythonhosted.org/packages/09/93/d7ed73f82b6f1045dd5d98f063fa16da5273d0812c42f38229d28882762b/greenlet-2.0.2-cp39-cp39-win32.whl", hash = "sha256:ea9872c80c132f4663822dd2a08d404073a5a9b5ba6155bea72fb2a79d1093b5"}, + {url = "https://files.pythonhosted.org/packages/0a/46/96b37dcfe9c9d39b2d2f060a5775139ce8a440315a1ca2667a6b83a2860e/greenlet-2.0.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df"}, + {url = "https://files.pythonhosted.org/packages/0a/54/cbc1096b883b2d1c0c1454837f089971de814ba5ce42be04cf0716a06000/greenlet-2.0.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0f72c9ddb8cd28532185f54cc1453f2c16fb417a08b53a855c4e6a418edd099"}, + {url = "https://files.pythonhosted.org/packages/0d/f6/2d406a22767029e785154071bef79b296f91b92d1c199ec3c2202386bf04/greenlet-2.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b"}, + {url = "https://files.pythonhosted.org/packages/17/f9/7f5d755380d329e44307c2f6e52096740fdebb92e7e22516811aeae0aec0/greenlet-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91"}, + {url = "https://files.pythonhosted.org/packages/1d/a0/697653ea5e35acaf28e2a1246645ac512beb9b49a86b310fd0151b075342/greenlet-2.0.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6"}, + {url = "https://files.pythonhosted.org/packages/1e/1e/632e55a04d732c8184201238d911207682b119c35cecbb9a573a6c566731/greenlet-2.0.2.tar.gz", hash = "sha256:e7c8dc13af7db097bed64a051d2dd49e9f0af495c26995c00a9ee842690d34c0"}, + {url = "https://files.pythonhosted.org/packages/1f/42/95800f165d20fb8269fe6a3ac494649718ede074b1d8a78f58ee2ebda27a/greenlet-2.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:18e98fb3de7dba1c0a852731c3070cf022d14f0d68b4c87a19cc1016f3bb8b33"}, + {url = "https://files.pythonhosted.org/packages/20/28/c93ffaa75f3c907cd010bf44c5c18c7f8f4bb2409146bd67d538163e33b8/greenlet-2.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1a819eef4b0e0b96bb0d98d797bef17dc1b4a10e8d7446be32d1da33e095dbb8"}, + {url = "https://files.pythonhosted.org/packages/29/c4/fe82cb9ff1bffc52a3832e35fa49cce63e5d366808179153ee879ce47cc9/greenlet-2.0.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9d14b83fab60d5e8abe587d51c75b252bcc21683f24699ada8fb275d7712f5a9"}, + {url = "https://files.pythonhosted.org/packages/37/b9/3ebd606768bee3ef2198fe6d5e7c6c3af42ad3e06b56c1d0a89c56faba2a/greenlet-2.0.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b"}, + {url = "https://files.pythonhosted.org/packages/3a/69/a6d3d7abd0f36438ff5fab52572fd107966939d59ef9b8309263ab89f607/greenlet-2.0.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:910841381caba4f744a44bf81bfd573c94e10b3045ee00de0cbf436fe50673a6"}, + {url = "https://files.pythonhosted.org/packages/42/d0/285b81442d8552b1ae6a2ff38caeec94ab90507c9740da718189416e8e6e/greenlet-2.0.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:bdfea8c661e80d3c1c99ad7c3ff74e6e87184895bbaca6ee8cc61209f8b9b85d"}, + {url = "https://files.pythonhosted.org/packages/43/81/e0a656e3a417b172f834ba5a08dde02b55fd249416c1e933d62ffb6734d0/greenlet-2.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2780572ec463d44c1d3ae850239508dbeb9fed38e294c68d19a24d925d9223ca"}, + {url = "https://files.pythonhosted.org/packages/49/b8/3ee1723978245e6f0c087908689f424876803ec05555400681240ab2ab33/greenlet-2.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be4ed120b52ae4d974aa40215fcdfde9194d63541c7ded40ee12eb4dda57b76b"}, + {url = "https://files.pythonhosted.org/packages/4d/b2/32f737e1fcf67b23351b4860489029df562b41d7ffb568a3e1ae610f7a9b/greenlet-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7efde645ca1cc441d6dc4b48c0f7101e8d86b54c8530141b09fd31cef5149ec9"}, + {url = "https://files.pythonhosted.org/packages/50/3d/7e3d95b955722c514f982bdf6bbe92bb76218b0036dd9b093ae0c168d63a/greenlet-2.0.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30"}, + {url = "https://files.pythonhosted.org/packages/52/39/fa5212bc9ac588c62e52213d4fab30a348059842883410724f9d0408c0f4/greenlet-2.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343"}, + {url = "https://files.pythonhosted.org/packages/53/0f/637f6e18e1980ebd2eedd8a9918a7898a6fe44f6188f6f39c6d9181c9891/greenlet-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645"}, + {url = "https://files.pythonhosted.org/packages/54/ce/3a589ec27bd5de97707d2a193716bbe412ccbdb1479f0c3f990789c8fa8c/greenlet-2.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9c59a2120b55788e800d82dfa99b9e156ff8f2227f07c5e3012a45a399620b7"}, + {url = "https://files.pythonhosted.org/packages/57/a8/079c59b8f5406957224f4f4176e9827508d555beba6d8635787d694226d1/greenlet-2.0.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:18a7f18b82b52ee85322d7a7874e676f34ab319b9f8cce5de06067384aa8ff43"}, + {url = "https://files.pythonhosted.org/packages/5a/30/5eab5cbb99263c7d8305657587381c84da2a71fddb07dd5efbfaeecf7264/greenlet-2.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937e9020b514ceedb9c830c55d5c9872abc90f4b5862f89c0887033ae33c6f73"}, + {url = "https://files.pythonhosted.org/packages/6a/3d/77bd8dd7dd0b872eac87f1edf6fcd94d9d7666befb706ae3a08ed25fbea7/greenlet-2.0.2-cp36-cp36m-win32.whl", hash = "sha256:dbfcfc0218093a19c252ca8eb9aee3d29cfdcb586df21049b9d777fd32c14fd9"}, + {url = "https://files.pythonhosted.org/packages/6b/2f/1cb3f376df561c95cb61b199676f51251f991699e325a2aa5e12693d10b8/greenlet-2.0.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0"}, + {url = "https://files.pythonhosted.org/packages/6b/cd/84301cdf80360571f6aa77ac096f867ba98094fec2cb93e69c93d996b8f8/greenlet-2.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a"}, + {url = "https://files.pythonhosted.org/packages/6e/11/a1f1af20b6a1a8069bc75012569d030acb89fd7ef70f888b6af2f85accc6/greenlet-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d75209eed723105f9596807495d58d10b3470fa6732dd6756595e89925ce2470"}, + {url = "https://files.pythonhosted.org/packages/71/c5/c26840ce91bcbbfc42c1a246289d9d4c758663652669f24e37f84bcdae2a/greenlet-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5454276c07d27a740c5892f4907c86327b632127dd9abec42ee62e12427ff7e3"}, + {url = "https://files.pythonhosted.org/packages/7c/5f/ee39d27a08ae6b93f14faa953a6593dad888df75ae55ff479135e64ad4fe/greenlet-2.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acd2162a36d3de67ee896c43effcd5ee3de247eb00354db411feb025aa319857"}, + {url = "https://files.pythonhosted.org/packages/7c/f8/275f7fb1585d5e7dfbc18b4eb78282fbc85986f2eb8a185e7ebc60522cc2/greenlet-2.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:703f18f3fda276b9a916f0934d2fb6d989bf0b4fb5a64825260eb9bfd52d78f0"}, + {url = "https://files.pythonhosted.org/packages/7e/a6/0a34cde83fe520fa4e8192a1bc0fc7bf9f755215fefe3f42c9b97c45c620/greenlet-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:7cafd1208fdbe93b67c7086876f061f660cfddc44f404279c1585bbf3cdc64c5"}, + {url = "https://files.pythonhosted.org/packages/83/d1/cc273f8f5908940d6666a3db8637d2e24913a2e8e5034012b19ac291a2a0/greenlet-2.0.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:3c9b12575734155d0c09d6c3e10dbd81665d5c18e1a7c6597df72fd05990c8cf"}, + {url = "https://files.pythonhosted.org/packages/86/8d/3a18311306830f6db5f5676a1cb8082c8943bfa6c928b40006e5358170fc/greenlet-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2"}, + {url = "https://files.pythonhosted.org/packages/93/40/db2803f88326149ddcd1c00092e1e36ef55d31922812863753143a9aca01/greenlet-2.0.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd021c754b162c0fb55ad5d6b9d960db667faad0fa2ff25bb6e1301b0b6e6a75"}, + {url = "https://files.pythonhosted.org/packages/9d/ae/8ee23a9b63f854acc66ed0da7220130d87c861153cbc8ea07d11b61567f1/greenlet-2.0.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b9ec052b06a0524f0e35bd8790686a1da006bd911dd1ef7d50b77bfbad74e292"}, + {url = "https://files.pythonhosted.org/packages/a1/ea/66e69cf3034be99a1959b2bdd178f5176979e0e63107a37a194c90c49b40/greenlet-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40"}, + {url = "https://files.pythonhosted.org/packages/a3/6c/dde49c63ab2f12d2ce401620dbe1a80830109f5f310bdd2f96d2e259de37/greenlet-2.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:9f35ec95538f50292f6d8f2c9c9f8a3c6540bbfec21c9e5b4b751e0a7c20864f"}, + {url = "https://files.pythonhosted.org/packages/a8/7a/5542d863a91b3309585219bae7d97aa82fe0482499a840c100297262ec8f/greenlet-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c"}, + {url = "https://files.pythonhosted.org/packages/aa/21/6bbd8062fee551f747f5334b7ccd503693704ac4f3183fd8232e2af77bff/greenlet-2.0.2-cp35-cp35m-win32.whl", hash = "sha256:03a8f4f3430c3b3ff8d10a2a86028c660355ab637cee9333d63d66b56f09d52a"}, + {url = "https://files.pythonhosted.org/packages/ac/4a/3ceafef892b8428f77468506bc5a12d835fb9f150129d1a9704902cb4a2a/greenlet-2.0.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4b58adb399c4d61d912c4c331984d60eb66565175cdf4a34792cd9600f21b394"}, + {url = "https://files.pythonhosted.org/packages/b3/89/1d3b78577a6b2762cb254f6ce5faec9b7c7b23052d1cdb7237273ff37d10/greenlet-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:db1a39669102a1d8d12b57de2bb7e2ec9066a6f2b3da35ae511ff93b01b5d564"}, + {url = "https://files.pythonhosted.org/packages/c4/92/bbd9373fb022c21d1c41bc74b043d8d007825f80bb9534f0dd2f7ed62bca/greenlet-2.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a51c9751078733d88e013587b108f1b7a1fb106d402fb390740f002b6f6551a"}, + {url = "https://files.pythonhosted.org/packages/c5/ab/a69a875a45474cc5776b879258bfa685e99aae992ab310a0b8f773fe56a0/greenlet-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088"}, + {url = "https://files.pythonhosted.org/packages/c7/c9/2637e49b0ef3f17d7eaa52c5af5bfbda5f058e8ee97bd9418978b90e1169/greenlet-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526"}, + {url = "https://files.pythonhosted.org/packages/ca/1a/90f2ae7e3df48cbd42af5df47cf9ee37a6c6a78b1941acbc7eac029f5a44/greenlet-2.0.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae"}, + {url = "https://files.pythonhosted.org/packages/cd/e8/1ebc8f07d795c3677247e37dae23463a655636a4be387b0d739fa8fd9b2f/greenlet-2.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb"}, + {url = "https://files.pythonhosted.org/packages/d2/28/5cf37650334935c6a51313c70c4ec00fb1fad801a551c36afcfc9c03e80b/greenlet-2.0.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:32e5b64b148966d9cccc2c8d35a671409e45f195864560829f395a54226408d3"}, + {url = "https://files.pythonhosted.org/packages/d6/c4/f91d771a6628155676765c419c70d6d0ede9b5f3c023102c47ee2f45eadf/greenlet-2.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:36abbf031e1c0f79dd5d596bfaf8e921c41df2bdf54ee1eed921ce1f52999a86"}, + {url = "https://files.pythonhosted.org/packages/da/45/2600faf65f318767d2c24b6fce6bb0ad3721e8cb3eb9d7743aefcca8a6a6/greenlet-2.0.2-cp38-cp38-win32.whl", hash = "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249"}, + {url = "https://files.pythonhosted.org/packages/e5/ad/91a8f63881c862bb396cefc33d7faa241bf200df7ba96a1961a99329ed15/greenlet-2.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0bf60faf0bc2468089bdc5edd10555bab6e85152191df713e2ab1fcc86382b5a"}, + {url = "https://files.pythonhosted.org/packages/e6/0e/591ea935b63aa3aed3836976779e5d1324aa4b2961f7355ff5d1f296066b/greenlet-2.0.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:f82d4d717d8ef19188687aa32b8363e96062911e63ba22a0cff7802a8e58e5f1"}, + {url = "https://files.pythonhosted.org/packages/e8/3a/ebc4fa1e813ae1fa718eb88417c31587e2efb743ed5f6ff0ae066502c349/greenlet-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca"}, + {url = "https://files.pythonhosted.org/packages/e9/29/2ae545c4c0218b042c2bb0760c0f65e114cca1ab5e552dc23b0f118e428a/greenlet-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94c817e84245513926588caf1152e3b559ff794d505555211ca041f032abbb6b"}, + {url = "https://files.pythonhosted.org/packages/f0/2e/20eab0fa6353a08b0de055dd54e2575a6869ee693d86387076430475832d/greenlet-2.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:eff4eb9b7eb3e4d0cae3d28c283dc16d9bed6b193c2e1ace3ed86ce48ea8df19"}, + {url = "https://files.pythonhosted.org/packages/f4/ad/287efe1d3c8224fa5f9457195a842fc0c4fa4956cb9655a1f4e89914a313/greenlet-2.0.2-cp27-cp27m-win32.whl", hash = "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74"}, + {url = "https://files.pythonhosted.org/packages/f6/04/74e97d545f9276dee994b959eab3f7d70d77588e5aaedc383d15b0057acd/greenlet-2.0.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8"}, + {url = "https://files.pythonhosted.org/packages/fa/9a/e0e99a4aa93b16dd58881acb55ac1e2fb011475f2e46cf87843970001882/greenlet-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3"}, + {url = "https://files.pythonhosted.org/packages/fc/80/0ed0da38bbb978f39128d7e53ee51c36bed2e4a7460eff92981a3d07f1d4/greenlet-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7"}, +] +"h11 0.14.0" = [ + {url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] +"httpcore 0.17.3" = [ + {url = "https://files.pythonhosted.org/packages/63/ad/c98ecdbfe04417e71e143bf2f2fb29128e4787d78d1cedba21bd250c7e7a/httpcore-0.17.3.tar.gz", hash = "sha256:a6f30213335e34c1ade7be6ec7c47f19f50c56db36abef1a9dfa3815b1cb3888"}, + {url = "https://files.pythonhosted.org/packages/94/2c/2bde7ff8dd2064395555220cbf7cba79991172bf5315a07eb3ac7688d9f1/httpcore-0.17.3-py3-none-any.whl", hash = "sha256:c2789b767ddddfa2a5782e3199b2b7f6894540b17b16ec26b2c4d8e103510b87"}, +] +"httptools 0.6.0" = [ + {url = "https://files.pythonhosted.org/packages/0f/40/26a5bd40bf12c40345fe7eb123e0077162f8896ee079af153b3ffe909285/httptools-0.6.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:73e9d66a5a28b2d5d9fbd9e197a31edd02be310186db423b28e6052472dc8201"}, + {url = "https://files.pythonhosted.org/packages/1b/51/ad5ec00731c00a4679ac2d8aaaf439579974fec969d52ec0300a9ec821e2/httptools-0.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:72ec7c70bd9f95ef1083d14a755f321d181f046ca685b6358676737a5fecd26a"}, + {url = "https://files.pythonhosted.org/packages/24/25/f6d51f2f464411eb103e56caeec0d215efb4cddd95f87d708c6def7aa848/httptools-0.6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fb4a608c631f7dcbdf986f40af7a030521a10ba6bc3d36b28c1dc9e9035a3c0"}, + {url = "https://files.pythonhosted.org/packages/25/34/787a9035536f00641e41c9aaead1477d1538c18a8606aa61c9edca7f136c/httptools-0.6.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:26326e0a8fe56829f3af483200d914a7cd16d8d398d14e36888b56de30bec81a"}, + {url = "https://files.pythonhosted.org/packages/26/3d/7d236d77a8ff9306137df7aa3bc6a79c8c53f78f13fb8ad9626a9be1aec2/httptools-0.6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35a541579bed0270d1ac10245a3e71e5beeb1903b5fbbc8d8b4d4e728d48ff1d"}, + {url = "https://files.pythonhosted.org/packages/27/19/b13b3815aa50cffcbd00f9505d35e435781cdcecebd7cd4242b0f5b4f544/httptools-0.6.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3f96d2a351b5625a9fd9133c95744e8ca06f7a4f8f0b8231e4bbaae2c485046a"}, + {url = "https://files.pythonhosted.org/packages/27/c1/58ad85d57a528bff8edd8b9004deadaded11b1195c58b79a39f600c05377/httptools-0.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4e748fc0d5c4a629988ef50ac1aef99dfb5e8996583a73a717fc2cac4ab89932"}, + {url = "https://files.pythonhosted.org/packages/28/7c/5ddc99737fb141bd9077f45af23e9d3c83496b4c04bf463e4e72f57043cd/httptools-0.6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82c723ed5982f8ead00f8e7605c53e55ffe47c47465d878305ebe0082b6a1755"}, + {url = "https://files.pythonhosted.org/packages/2b/15/a48d8036bf6ed80201f41479df1813ad1e01b48284281edcbefd05c3a364/httptools-0.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:23b09537086a5a611fad5696fc8963d67c7e7f98cb329d38ee114d588b0b74cd"}, + {url = "https://files.pythonhosted.org/packages/30/7c/9821f018649fb3d175df0293adea3b9158edb75c1328853448f02d52323d/httptools-0.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b703d15dbe082cc23266bf5d9448e764c7cb3fcfe7cb358d79d3fd8248673ef9"}, + {url = "https://files.pythonhosted.org/packages/36/9c/8f237d959e047b1c6d00131c063ce7c5ca48a64fd9b845c94bcb56c9d98c/httptools-0.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72205730bf1be875003692ca54a4a7c35fac77b4746008966061d9d41a61b0f5"}, + {url = "https://files.pythonhosted.org/packages/45/6c/e5e256103be70d24319f43266be83d1d8c9bfce39c806602601b0635845e/httptools-0.6.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:cf8169e839a0d740f3d3c9c4fa630ac1a5aaf81641a34575ca6773ed7ce041a1"}, + {url = "https://files.pythonhosted.org/packages/4e/4d/21f6a90385a54a05dcda1ca61835bed2aad3dd9003d71ed34265a2a1284c/httptools-0.6.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:93f89975465133619aea8b1952bc6fa0e6bad22a447c6d982fc338fbb4c89649"}, + {url = "https://files.pythonhosted.org/packages/51/9d/638ce3ce7ef549f6bb6b2b89369b278e8fd4b47cbebc1bf028e1a68608ac/httptools-0.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d0b0571806a5168013b8c3d180d9f9d6997365a4212cb18ea20df18b938aa0b"}, + {url = "https://files.pythonhosted.org/packages/5e/62/d7620a822006cb61506ed730cf622c0678cb89fa03372820e9d328cd4a67/httptools-0.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:259920bbae18740a40236807915def554132ad70af5067e562f4660b62c59b90"}, + {url = "https://files.pythonhosted.org/packages/61/46/c06a1f2a35d961204c1fd38700220df71ddcf5d01ff0f4cae73e28b67480/httptools-0.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:274bf20eeb41b0956e34f6a81f84d26ed57c84dd9253f13dcb7174b27ccd8aaf"}, + {url = "https://files.pythonhosted.org/packages/62/c7/eca648bc8eb24caf5e1614e80d3a189187eec3ce50c6f4dda545398eb267/httptools-0.6.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6e22896b42b95b3237eccc42278cd72c0df6f23247d886b7ded3163452481e38"}, + {url = "https://files.pythonhosted.org/packages/73/0f/2a76cef72e35b0696bf61d2458eaff3b5c1ac728e9090b6a87045d65e1c9/httptools-0.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33eb1d4e609c835966e969a31b1dedf5ba16b38cab356c2ce4f3e33ffa94cad3"}, + {url = "https://files.pythonhosted.org/packages/80/e6/158e8d56f6b4d29295cfa244f1e79a65d0987bb3941d36863e7f6e06e3b6/httptools-0.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:38f3cafedd6aa20ae05f81f2e616ea6f92116c8a0f8dcb79dc798df3356836e2"}, + {url = "https://files.pythonhosted.org/packages/83/e0/5c87bf475dd666bed3e8a949cb2f098b296a74bc534cd6882098aeb0d41c/httptools-0.6.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b0a816bb425c116a160fbc6f34cece097fd22ece15059d68932af686520966bd"}, + {url = "https://files.pythonhosted.org/packages/85/df/63720eadbadca00b7f91bc724972ca3a946670598354bef1de779c7c62e2/httptools-0.6.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:721e503245d591527cddd0f6fd771d156c509e831caa7a57929b55ac91ee2b51"}, + {url = "https://files.pythonhosted.org/packages/8a/a6/4ee791339f70776a0d57bd066f3721a24491a8b4b4faf572036d163296d1/httptools-0.6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03bfd2ae8a2d532952ac54445a2fb2504c804135ed28b53fefaf03d3a93eb1fd"}, + {url = "https://files.pythonhosted.org/packages/8f/71/d535e9f6967958d21b8fe1baeb7efb6304b86e8fcff44d0bda8690e0aec9/httptools-0.6.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:818325afee467d483bfab1647a72054246d29f9053fd17cc4b86cda09cc60339"}, + {url = "https://files.pythonhosted.org/packages/93/eb/dfa4990960c1c3a75d2b5a75e44f358a94530a988e2380ebc29782166fe5/httptools-0.6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:47043a6e0ea753f006a9d0dd076a8f8c99bc0ecae86a0888448eb3076c43d717"}, + {url = "https://files.pythonhosted.org/packages/a0/35/3861af367612c20b74862d474c495c2818037287118ed0d3e598cf48439a/httptools-0.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5dcc14c090ab57b35908d4a4585ec5c0715439df07be2913405991dbb37e049d"}, + {url = "https://files.pythonhosted.org/packages/ae/73/eae64945f3bc9d25a57cb1bc35e8fdf2247a9fd027b1749855aeedcb0bcb/httptools-0.6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65d802e7b2538a9756df5acc062300c160907b02e15ed15ba035b02bce43e89c"}, + {url = "https://files.pythonhosted.org/packages/bf/51/1e2f9691821f715ac63b9c8f6592a36c9025d09bf0ecf19832290480ee8e/httptools-0.6.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:dea66d94e5a3f68c5e9d86e0894653b87d952e624845e0b0e3ad1c733c6cc75d"}, + {url = "https://files.pythonhosted.org/packages/c5/fa/aced15396316b401e74b587610b503ce2b0613b027188f1456bca164ce94/httptools-0.6.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:463c3bc5ef64b9cf091be9ac0e0556199503f6e80456b790a917774a616aff6e"}, + {url = "https://files.pythonhosted.org/packages/cf/09/b17fbf88d5c285e7cd8162539ba6f95c778dcd47e44240aa14afd0982bb8/httptools-0.6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdc6675ec6cb79d27e0575750ac6e2b47032742e24eed011b8db73f2da9ed40"}, + {url = "https://files.pythonhosted.org/packages/d5/63/f1594d00b4ef9c137edc0ff202d84e684b6989f9b8b4d1475098ca320c9d/httptools-0.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:0781fedc610293a2716bc7fa142d4c85e6776bc59d617a807ff91246a95dea35"}, + {url = "https://files.pythonhosted.org/packages/dc/ba/97266d7c207fb6572bb8ec4154849812b26169442e17aae0e58cc7ceb7fb/httptools-0.6.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f959e4770b3fc8ee4dbc3578fd910fab9003e093f20ac8c621452c4d62e517cb"}, + {url = "https://files.pythonhosted.org/packages/dd/d7/79d8374e5aebd87c630b349313771fc1b24bf377af7e429b12c17979690b/httptools-0.6.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e41ccac9e77cd045f3e4ee0fc62cbf3d54d7d4b375431eb855561f26ee7a9ec4"}, + {url = "https://files.pythonhosted.org/packages/e3/3f/e976f6cf3da5e9dbda0096a0e65c1b109036a562658e020971ec54d007fb/httptools-0.6.0.tar.gz", hash = "sha256:9fc6e409ad38cbd68b177cd5158fc4042c796b82ca88d99ec78f07bed6c6b796"}, + {url = "https://files.pythonhosted.org/packages/ef/3f/dc03c90b23107ff68c3b8a48008a6f0bc07628bee900f187356a57c0dbe6/httptools-0.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:22c01fcd53648162730a71c42842f73b50f989daae36534c818b3f5050b54589"}, + {url = "https://files.pythonhosted.org/packages/ef/89/00b9805ac1205572afa4f2042542365b8cecf53ffddd1df635fcdde6fa0d/httptools-0.6.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:82f228b88b0e8c6099a9c4757ce9fdbb8b45548074f8d0b1f0fc071e35655d1c"}, +] +"httpx 0.24.1" = [ + {url = "https://files.pythonhosted.org/packages/ec/91/e41f64f03d2a13aee7e8c819d82ee3aa7cdc484d18c0ae859742597d5aa0/httpx-0.24.1-py3-none-any.whl", hash = "sha256:06781eb9ac53cde990577af654bd990a4949de37a28bdb4a230d434f3a30b9bd"}, + {url = "https://files.pythonhosted.org/packages/f8/2a/114d454cb77657dbf6a293e69390b96318930ace9cd96b51b99682493276/httpx-0.24.1.tar.gz", hash = "sha256:5853a43053df830c20f8110c5e69fe44d035d850b2dfe795e196f00fdb774bdd"}, +] +"identify 2.5.24" = [ + {url = "https://files.pythonhosted.org/packages/4f/fd/2c46fba2bc032ba4c970bb8de59d25187087d7138a0ebf7c1dcc91d94f01/identify-2.5.24-py2.py3-none-any.whl", hash = "sha256:986dbfb38b1140e763e413e6feb44cd731faf72d1909543178aa79b0e258265d"}, + {url = "https://files.pythonhosted.org/packages/c4/f8/498e13e408d25ee6ff04aa0acbf91ad8e9caae74be91720fc0e811e649b7/identify-2.5.24.tar.gz", hash = "sha256:0aac67d5b4812498056d28a9a512a483f5085cc28640b02b258a59dac34301d4"}, +] +"idna 3.4" = [ + {url = "https://files.pythonhosted.org/packages/8b/e1/43beb3d38dba6cb420cefa297822eac205a277ab43e5ba5d5c46faf96438/idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, + {url = "https://files.pythonhosted.org/packages/fc/34/3030de6f1370931b9dbb4dad48f6ab1015ab1d32447850b9fc94e60097be/idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, +] +"importlib-metadata 6.8.0" = [ + {url = "https://files.pythonhosted.org/packages/33/44/ae06b446b8d8263d712a211e959212083a5eda2bf36d57ca7415e03f6f36/importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, + {url = "https://files.pythonhosted.org/packages/cc/37/db7ba97e676af155f5fcb1a35466f446eadc9104e25b83366e8088c9c926/importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, +] +"importlib-resources 6.0.0" = [ + {url = "https://files.pythonhosted.org/packages/29/d1/bed03eca30aa05aaf6e0873de091f9385c48705c4a607c2dfe3edbe543e8/importlib_resources-6.0.0-py3-none-any.whl", hash = "sha256:d952faee11004c045f785bb5636e8f885bed30dc3c940d5d42798a2a4541c185"}, + {url = "https://files.pythonhosted.org/packages/78/1f/65a619c18b0ecd55ac165c7ed119c846051991d01c2cfc0ff7818e4573f0/importlib_resources-6.0.0.tar.gz", hash = "sha256:4cf94875a8368bd89531a756df9a9ebe1f150e0f885030b461237bc7f2d905f2"}, +] +"iniconfig 2.0.0" = [ + {url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, + {url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, +] +"isort 5.12.0" = [ + {url = "https://files.pythonhosted.org/packages/0a/63/4036ae70eea279c63e2304b91ee0ac182f467f24f86394ecfe726092340b/isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"}, + {url = "https://files.pythonhosted.org/packages/a9/c4/dc00e42c158fc4dda2afebe57d2e948805c06d5169007f1724f0683010a9/isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"}, +] +"lazy-object-proxy 1.9.0" = [ + {url = "https://files.pythonhosted.org/packages/00/74/46a68f51457639c0cd79e385e2f49c0fa7324470997ac096108669c1e182/lazy_object_proxy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:946d27deaff6cf8452ed0dba83ba38839a87f4f7a9732e8f9fd4107b21e6ff07"}, + {url = "https://files.pythonhosted.org/packages/11/04/fa820296cb937b378d801cdc81c19de06624cfed481c1b8a6b439255a188/lazy_object_proxy-1.9.0-cp37-cp37m-win32.whl", hash = "sha256:f12ad7126ae0c98d601a7ee504c1122bcef553d1d5e0c3bfa77b16b3968d2734"}, + {url = "https://files.pythonhosted.org/packages/11/fe/be1eb76d83f1b5242c492b410ce86c59db629c0b0f0f8e75018dfd955c30/lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f5fa4a61ce2438267163891961cfd5e32ec97a2c444e5b842d574251ade27d2"}, + {url = "https://files.pythonhosted.org/packages/16/f2/e74981dedeb1a858cd5db9bcec81c4107da374249bc6894613472e01996f/lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0117049dd1d5635bbff65444496c90e0baa48ea405125c088e93d9cf4525b11"}, + {url = "https://files.pythonhosted.org/packages/18/1b/04ac4490a62ae1916f88e629e74192ada97d74afc927453d005f003e5a8f/lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f83ac4d83ef0ab017683d715ed356e30dd48a93746309c8f3517e1287523ef4"}, + {url = "https://files.pythonhosted.org/packages/1d/5d/25b9007c65f45805e711b56beac50ba395214e9e556cc8ee57f0882f88a9/lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8c6cfb338b133fbdbc5cfaa10fe3c6aeea827db80c978dbd13bc9dd8526b7d4"}, + {url = "https://files.pythonhosted.org/packages/20/c0/8bab72a73607d186edad50d0168ca85bd2743cfc55560c9d721a94654b20/lazy-object-proxy-1.9.0.tar.gz", hash = "sha256:659fb5809fa4629b8a1ac5106f669cfc7bef26fbb389dda53b3e010d1ac4ebae"}, + {url = "https://files.pythonhosted.org/packages/27/a1/7cc10ca831679c5875c18ae6e0a468f7787ecd31fdd53598f91ea50df58d/lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:721532711daa7db0d8b779b0bb0318fa87af1c10d7fe5e52ef30f8eff254d0cd"}, + {url = "https://files.pythonhosted.org/packages/31/ad/e8605300f51061284cc57ca0f4ef582047c7f309bda1bb1c3c19b64af5c9/lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66a3de4a3ec06cd8af3f61b8e1ec67614fbb7c995d02fa224813cb7afefee701"}, + {url = "https://files.pythonhosted.org/packages/4c/a4/cdd6f41a675a89ab584c78019a24adc533829764bcc85b0e24ed2678020c/lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7322c3d6f1766d4ef1e51a465f47955f1e8123caee67dd641e67d539a534d006"}, + {url = "https://files.pythonhosted.org/packages/4d/7b/a959ff734bd3d8df7b761bfeaec6428549b77267072676a337b774f3b3ef/lazy_object_proxy-1.9.0-cp310-cp310-win32.whl", hash = "sha256:f0705c376533ed2a9e5e97aacdbfe04cecd71e0aa84c7c0595d02ef93b6e4455"}, + {url = "https://files.pythonhosted.org/packages/4e/cb/aca3f4d89d3efbed724fd9504a96dafbe2d903ea908355a335acb110a5cd/lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:660c94ea760b3ce47d1855a30984c78327500493d396eac4dfd8bd82041b22be"}, + {url = "https://files.pythonhosted.org/packages/51/28/5c6dfb51df2cbb6d771a3b0d009f1edeab01f5cb16303ce32764b01636c0/lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfb38f9ffb53b942f2b5954e0f610f1e721ccebe9cce9025a38c8ccf4a5183a4"}, + {url = "https://files.pythonhosted.org/packages/5b/a6/3c0a8b2ad6ce7af133ed54321b0ead5509303be3a80f98506af198e50cb7/lazy_object_proxy-1.9.0-cp38-cp38-win32.whl", hash = "sha256:0a891e4e41b54fd5b8313b96399f8b0e173bbbfc03c7631f01efbe29bb0bcf82"}, + {url = "https://files.pythonhosted.org/packages/5c/76/0b16dc53e9ee5b24c621d808f46cca11e5e86c602b6bcd6dc27f9504af5b/lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:09763491ce220c0299688940f8dc2c5d05fd1f45af1e42e636b2e8b2303e4382"}, + {url = "https://files.pythonhosted.org/packages/69/1f/51657d681711476287c9ff643428be0f9663addc1d341d4be1bad89290bd/lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e7c21c95cae3c05c14aafffe2865bbd5e377cfc1348c4f7751d9dc9a48ca4bda"}, + {url = "https://files.pythonhosted.org/packages/69/da/58391196d8a41fa8fa69b47e8a7893f279d369939e4994b3bc8648ff0433/lazy_object_proxy-1.9.0-cp39-cp39-win32.whl", hash = "sha256:9090d8e53235aa280fc9239a86ae3ea8ac58eff66a705fa6aa2ec4968b95c821"}, + {url = "https://files.pythonhosted.org/packages/70/e7/f3735f8e47cb29a207568c5b8d28d9f5673228789b66cb0c48d488a37f94/lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:212774e4dfa851e74d393a2370871e174d7ff0ebc980907723bb67d25c8a7c30"}, + {url = "https://files.pythonhosted.org/packages/82/ac/d079d3ad377ba72e29d16ac077f8626ad4d3f55369c93168d0b81153d9a2/lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:18b78ec83edbbeb69efdc0e9c1cb41a3b1b1ed11ddd8ded602464c3fc6020494"}, + {url = "https://files.pythonhosted.org/packages/86/93/e921f7a795e252df7248e0d220dc69a9443ad507fe258dea51a32e5435ca/lazy_object_proxy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b40387277b0ed2d0602b8293b94d7257e17d1479e257b4de114ea11a8cb7f2d7"}, + {url = "https://files.pythonhosted.org/packages/8d/6d/10420823a979366bf43ca5e69433c0c588865883566b96b6e3ed5b51c1f8/lazy_object_proxy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:f2457189d8257dd41ae9b434ba33298aec198e30adf2dcdaaa3a28b9994f6adb"}, + {url = "https://files.pythonhosted.org/packages/9d/d7/81d466f2e69784bd416797824a2b8794aaf0b864a2390062ea197f06f0fc/lazy_object_proxy-1.9.0-cp311-cp311-win32.whl", hash = "sha256:81fc4d08b062b535d95c9ea70dbe8a335c45c04029878e62d744bdced5141586"}, + {url = "https://files.pythonhosted.org/packages/a7/51/6626c133e966698d53d65bcd90e34ad4986c5f0968c2328b3e9de51dbcf1/lazy_object_proxy-1.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9e25ef10a39e8afe59a5c348a4dbf29b4868ab76269f81ce1674494e2565a6e"}, + {url = "https://files.pythonhosted.org/packages/a8/32/c1a67f76ec6923a8a8b1bc006b7cb3d195e386e03fe56e20fe38fce0321e/lazy_object_proxy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9e7551208b2aded9c1447453ee366f1c4070602b3d932ace044715d89666899b"}, + {url = "https://files.pythonhosted.org/packages/b0/78/78962cb6f6d31a952acbc54e7838a5a85d952144973bd6e7ad24323dd466/lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:189bbd5d41ae7a498397287c408617fe5c48633e7755287b21d741f7db2706a9"}, + {url = "https://files.pythonhosted.org/packages/b1/80/2d9583fa8e5ac47ef183d811d26d833477b7ed02b64c17dd2ede68a3f9cf/lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cbf9b082426036e19c6924a9ce90c740a9861e2bdc27a4834fd0a910742ac1e8"}, + {url = "https://files.pythonhosted.org/packages/c9/8f/c8aab72c72634de0c726a98a1e4c84a93ef20049ee0427c871214f6a58d5/lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f699ac1c768270c9e384e4cbd268d6e67aebcfae6cd623b4d7c3bfde5a35db59"}, + {url = "https://files.pythonhosted.org/packages/cd/b6/84efe6e878e94f20cf9564ac3ede5e98d37c692b07080aef50cc4341052e/lazy_object_proxy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0daa332786cf3bb49e10dc6a17a52f6a8f9601b4cf5c295a4f85854d61de63"}, + {url = "https://files.pythonhosted.org/packages/d0/f4/95999792ce5f9223bac10cb31b1724723ecd0ba13e081c5fb806d7f5b9c4/lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1aa3de4088c89a1b69f8ec0dcc169aa725b0ff017899ac568fe44ddc1396df46"}, + {url = "https://files.pythonhosted.org/packages/db/92/284ab10a6d0f82da36a20d9c1464c30bb318d1a6dd0ae476de9f890e7abd/lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8fa02eaab317b1e9e03f69aab1f91e120e7899b392c4fc19807a8278a07a97e8"}, + {url = "https://files.pythonhosted.org/packages/e7/86/ec93d495197f1006d7c9535e168fe763b3cc21928fb35c8f9ce08944b614/lazy_object_proxy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:ea806fd4c37bf7e7ad82537b0757999264d5f70c45468447bb2b91afdbe73a6e"}, + {url = "https://files.pythonhosted.org/packages/ed/9b/44c370c8bbba32fd0217b4f15ca99f750d669d653c7f1eefa051627710e8/lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cd077f3d04a58e83d04b20e334f678c2b0ff9879b9375ed107d5d07ff160171"}, + {url = "https://files.pythonhosted.org/packages/f5/4f/9ad496dc26a10ed9ab8f088732f08dc1f88488897d6c9ac5e3432a254c30/lazy_object_proxy-1.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:edd20c5a55acb67c7ed471fa2b5fb66cb17f61430b7a6b9c3b4a1e40293b1671"}, + {url = "https://files.pythonhosted.org/packages/fb/f4/c5d6d771e70ec7a9483a98054e8a5f386eda5b18b6c96544d251558c6c92/lazy_object_proxy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:9990d8e71b9f6488e91ad25f322898c136b008d87bf852ff65391b004da5e17b"}, + {url = "https://files.pythonhosted.org/packages/fc/8d/8e0fbfeec6e51184326e0886443e44142ce22d89fa9e9c3152900e654fa0/lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79a31b086e7e68b24b99b23d57723ef7e2c6d81ed21007b6281ebcd1688acb0a"}, + {url = "https://files.pythonhosted.org/packages/fe/bb/0fcc8585ffb7285df94382e20b54d54ca62a1bcf594f6f18d8feb3fc3b98/lazy_object_proxy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:db1c1722726f47e10e0b5fdbf15ac3b8adb58c091d12b3ab713965795036985f"}, +] +"mako 1.2.4" = [ + {url = "https://files.pythonhosted.org/packages/03/3b/68690a035ba7347860f1b8c0cde853230ba69ff41df5884ea7d89fe68cd3/Mako-1.2.4-py3-none-any.whl", hash = "sha256:c97c79c018b9165ac9922ae4f32da095ffd3c4e6872b45eded42926deea46818"}, + {url = "https://files.pythonhosted.org/packages/05/5f/2ba6e026d33a0e6ddc1dddf9958677f76f5f80c236bd65309d280b166d3e/Mako-1.2.4.tar.gz", hash = "sha256:d60a3903dc3bb01a18ad6a89cdbe2e4eadc69c0bc8ef1e3773ba53d44c3f7a34"}, +] +"markdown-it-py 3.0.0" = [ + {url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] +"markupsafe 2.1.3" = [ + {url = "https://files.pythonhosted.org/packages/03/06/e72e88f81f8c91d4f488d21712d2d403fd644e3172eaadc302094377bc22/MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, + {url = "https://files.pythonhosted.org/packages/03/65/3473d2cb84bb2cda08be95b97fc4f53e6bcd701a2d50ba7b7c905e1e9273/MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, + {url = "https://files.pythonhosted.org/packages/10/b3/c2b0a61cc0e1d50dd8a1b663ba4866c667cb58fb35f12475001705001680/MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, + {url = "https://files.pythonhosted.org/packages/12/b3/d9ed2c0971e1435b8a62354b18d3060b66c8cb1d368399ec0b9baa7c0ee5/MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, + {url = "https://files.pythonhosted.org/packages/20/1d/713d443799d935f4d26a4f1510c9e61b1d288592fb869845e5cc92a1e055/MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, + {url = "https://files.pythonhosted.org/packages/22/81/b5659e2b6ae1516495a22f87370419c1d79c8d853315e6cbe5172fc01a06/MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, + {url = "https://files.pythonhosted.org/packages/32/d4/ce98c4ca713d91c4a17c1a184785cc00b9e9c25699d618956c2b9999500a/MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, + {url = "https://files.pythonhosted.org/packages/3c/c8/74d13c999cbb49e3460bf769025659a37ef4a8e884de629720ab4e42dcdb/MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, + {url = "https://files.pythonhosted.org/packages/43/70/f24470f33b2035b035ef0c0ffebf57006beb2272cf3df068fc5154e04ead/MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, + {url = "https://files.pythonhosted.org/packages/43/ad/7246ae594aac948b17408c0ff0f9ff0bc470bdbe9c672a754310db64b237/MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, + {url = "https://files.pythonhosted.org/packages/44/53/93405d37bb04a10c43b1bdd6f548097478d494d7eadb4b364e3e1337f0cc/MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, + {url = "https://files.pythonhosted.org/packages/47/26/932140621773bfd4df3223fbdd9e78de3477f424f0d2987c313b1cb655ff/MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, + {url = "https://files.pythonhosted.org/packages/4d/e4/77bb622d6a37aeb51ee55857100986528b7f47d6dbddc35f9b404622ed50/MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, + {url = "https://files.pythonhosted.org/packages/4f/13/cf36eff21600fb21d5bd8c4c1b6ff0b7cc0ff37b955017210cfc6f367972/MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, + {url = "https://files.pythonhosted.org/packages/62/9b/4908a57acf39d8811836bc6776b309c2e07d63791485589acf0b6d7bc0c6/MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, + {url = "https://files.pythonhosted.org/packages/68/8d/c33c43c499c19f4b51181e196c9a497010908fc22c5de33551e298aa6a21/MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, + {url = "https://files.pythonhosted.org/packages/6a/86/654dc431513cd4417dfcead8102f22bece2d6abf2f584f0e1cc1524f7b94/MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, + {url = "https://files.pythonhosted.org/packages/6d/7c/59a3248f411813f8ccba92a55feaac4bf360d29e2ff05ee7d8e1ef2d7dbf/MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, + {url = "https://files.pythonhosted.org/packages/71/61/f5673d7aac2cf7f203859008bb3fc2b25187aa330067c5e9955e5c5ebbab/MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, + {url = "https://files.pythonhosted.org/packages/74/a3/54fc60ee2da3ab6d68b1b2daf4897297c597840212ee126e68a4eb89fcd7/MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, + {url = "https://files.pythonhosted.org/packages/7d/48/6ba4db436924698ca22109325969e00be459d417830dafec3c1001878b57/MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, + {url = "https://files.pythonhosted.org/packages/84/a8/c4aebb8a14a1d39d5135eb8233a0b95831cdc42c4088358449c3ed657044/MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, + {url = "https://files.pythonhosted.org/packages/8b/bb/72ca339b012054a84753accabe3258e0baf6e34bd0ab6e3670b9a65f679d/MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, + {url = "https://files.pythonhosted.org/packages/8d/66/4a46c7f1402e0377a8b220fd4b53cc4f1b2337ab0d97f06e23acd1f579d1/MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, + {url = "https://files.pythonhosted.org/packages/96/e4/4db3b1abc5a1fe7295aa0683eafd13832084509c3b8236f3faf8dd4eff75/MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, + {url = "https://files.pythonhosted.org/packages/9b/c1/9f44da5ca74f95116c644892152ca6514ecdc34c8297a3f40d886147863d/MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, + {url = "https://files.pythonhosted.org/packages/a2/b2/624042cb58cc6b3529a6c3a7b7d230766e3ecb768cba118ba7befd18ed6f/MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, + {url = "https://files.pythonhosted.org/packages/a2/f7/9175ad1b8152092f7c3b78c513c1bdfe9287e0564447d1c2d3d1a2471540/MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, + {url = "https://files.pythonhosted.org/packages/a6/56/f1d4ee39e898a9e63470cbb7fae1c58cce6874f25f54220b89213a47f273/MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, + {url = "https://files.pythonhosted.org/packages/a8/12/fd9ef3e09a7312d60467c71037283553ff2acfcd950159cd4c3ca9558af4/MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, + {url = "https://files.pythonhosted.org/packages/ab/20/f59423543a8422cb8c69a579ebd0ef2c9dafa70cc8142b7372b5b4073caa/MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, + {url = "https://files.pythonhosted.org/packages/b2/0d/cbaade3ee8efbd5ce2fb72b48cc51479ebf3d4ce2c54dcb6557d3ea6a950/MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, + {url = "https://files.pythonhosted.org/packages/b2/27/07e5aa9f93314dc65ad2ad9b899656dee79b70a9425ee199dd5a4c4cf2cd/MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, + {url = "https://files.pythonhosted.org/packages/bb/82/f88ccb3ca6204a4536cf7af5abdad7c3657adac06ab33699aa67279e0744/MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, + {url = "https://files.pythonhosted.org/packages/be/bb/08b85bc194034efbf572e70c3951549c8eca0ada25363afc154386b5390a/MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {url = "https://files.pythonhosted.org/packages/bf/b7/c5ba9b7ad9ad21fc4a60df226615cf43ead185d328b77b0327d603d00cc5/MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, + {url = "https://files.pythonhosted.org/packages/c0/c7/171f5ac6b065e1425e8fabf4a4dfbeca76fd8070072c6a41bd5c07d90d8b/MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, + {url = "https://files.pythonhosted.org/packages/c9/80/f08e782943ee7ae6e9438851396d00a869f5b50ea8c6e1f40385f3e95771/MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, + {url = "https://files.pythonhosted.org/packages/d2/a1/4ae49dd1520c7b891ea4963258aab08fb2554c564781ecb2a9c4afdf9cb1/MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, + {url = "https://files.pythonhosted.org/packages/d5/c1/1177f712d4ab91eb67f79d763a7b5f9c5851ee3077d6b4eee15e23b6b93e/MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, + {url = "https://files.pythonhosted.org/packages/de/63/cb7e71984e9159ec5f45b5e81e896c8bdd0e45fe3fc6ce02ab497f0d790e/MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, + {url = "https://files.pythonhosted.org/packages/de/e2/32c14301bb023986dff527a49325b6259cab4ebb4633f69de54af312fc45/MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, + {url = "https://files.pythonhosted.org/packages/e5/dd/49576e803c0d974671e44fa78049217fcc68af3662a24f831525ed30e6c7/MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, + {url = "https://files.pythonhosted.org/packages/e6/5c/8ab8f67bbbbf90fe88f887f4fa68123435c5415531442e8aefef1e118d5c/MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, + {url = "https://files.pythonhosted.org/packages/f4/a0/103f94793c3bf829a18d2415117334ece115aeca56f2df1c47fa02c6dbd6/MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, + {url = "https://files.pythonhosted.org/packages/f7/9c/86cbd8e0e1d81f0ba420f20539dd459c50537c7751e28102dbfee2b6f28c/MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, + {url = "https://files.pythonhosted.org/packages/f8/33/e9e83b214b5f8d9a60b26e60051734e7657a416e5bce7d7f1c34e26badad/MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, + {url = "https://files.pythonhosted.org/packages/fa/bb/12fb5964c4a766eb98155dd31ec070adc8a69a395564ffc1e7b34d91335a/MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, + {url = "https://files.pythonhosted.org/packages/fe/09/c31503cb8150cf688c1534a7135cc39bb9092f8e0e6369ec73494d16ee0e/MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, + {url = "https://files.pythonhosted.org/packages/fe/21/2eff1de472ca6c99ec3993eab11308787b9879af9ca8bbceb4868cf4f2ca/MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, +] +"mccabe 0.7.0" = [ + {url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] +"mdurl 0.1.2" = [ + {url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] +"msgpack 1.0.5" = [ + {url = "https://files.pythonhosted.org/packages/0a/04/bc319ba061f6dc9077745988be288705b3f9f18c5a209772a3e8fcd419fd/msgpack-1.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9985b214f33311df47e274eb788a5893a761d025e2b92c723ba4c63936b69b1"}, + {url = "https://files.pythonhosted.org/packages/0d/90/44edef4a8c6f035b054c4b017c5adcb22a35ec377e17e50dd5dced279a6b/msgpack-1.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:916723458c25dfb77ff07f4c66aed34e47503b2eb3188b3adbec8d8aa6e00f48"}, + {url = "https://files.pythonhosted.org/packages/0e/69/3d10e741dd2bbb806af5cdc76551735baab5f5f9773701eb05502c913a6e/msgpack-1.0.5-cp311-cp311-win32.whl", hash = "sha256:c396e2cc213d12ce017b686e0f53497f94f8ba2b24799c25d913d46c08ec422c"}, + {url = "https://files.pythonhosted.org/packages/10/ca/50c3a5e92d459a942169747315afd8c226d05427eccff903ddf33135c574/msgpack-1.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20a97bf595a232c3ee6d57ddaadd5453d174a52594bf9c21d10407e2a2d9b3bd"}, + {url = "https://files.pythonhosted.org/packages/10/fe/9e004c4deb457f1ef1ad88c1188da5691ff1855e0d03a5ac3635ae1f6530/msgpack-1.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:55b56a24893105dc52c1253649b60f475f36b3aa0fc66115bffafb624d7cb30b"}, + {url = "https://files.pythonhosted.org/packages/12/6e/0cfd1dc07f61a6ac606587a393f489c3ca463469d285a73c8e5e2f61b021/msgpack-1.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:20c784e66b613c7f16f632e7b5e8a1651aa5702463d61394671ba07b2fc9e025"}, + {url = "https://files.pythonhosted.org/packages/17/10/be97811782473d709d07b65a3955a5a76d47686aff3d62bb41d48aea7c92/msgpack-1.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:362d9655cd369b08fda06b6657a303eb7172d5279997abe094512e919cf74b11"}, + {url = "https://files.pythonhosted.org/packages/18/3f/3860151fbdf50e369bbe4ffd307a588417669c725025e383f3ce5893690f/msgpack-1.0.5-cp39-cp39-win32.whl", hash = "sha256:ac9dd47af78cae935901a9a500104e2dea2e253207c924cc95de149606dc43cc"}, + {url = "https://files.pythonhosted.org/packages/19/0c/2c3b443df88f5d400f2e19a3d867564d004b26e137f18c2f2663913987bc/msgpack-1.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:476a8fe8fae289fdf273d6d2a6cb6e35b5a58541693e8f9f019bfe990a51e4ba"}, + {url = "https://files.pythonhosted.org/packages/1a/f7/df5814697c25bdebb14ea97d27ddca04f5d4c6e249f096d086fea521c139/msgpack-1.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b72d0698f86e8d9ddf9442bdedec15b71df3598199ba33322d9711a19f08145c"}, + {url = "https://files.pythonhosted.org/packages/27/ad/4edfe383ec3185611441179ffee8cbc8155d7575fbad73f6d31015e35451/msgpack-1.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9f5ae84c5c8a857ec44dc180a8b0cc08238e021f57abdf51a8182e915e6299f0"}, + {url = "https://files.pythonhosted.org/packages/28/8f/c58c53c884217cc572c19349c7e1129b5a6eae36df0a017aae3a8f3d7aa8/msgpack-1.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:addab7e2e1fcc04bd08e4eb631c2a90960c340e40dfc4a5e24d2ff0d5a3b3edb"}, + {url = "https://files.pythonhosted.org/packages/29/56/1fb6b96aab759ab3bc05b03ba6d936b350db72aac203cde56ea6bd001237/msgpack-1.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d25dd59bbbbb996eacf7be6b4ad082ed7eacc4e8f3d2df1ba43822da9bfa122a"}, + {url = "https://files.pythonhosted.org/packages/2b/c4/f2c8695ae69d1425eddc5e2f849c525b562dc8409bc2979e525f3dd4fecd/msgpack-1.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56a62ec00b636583e5cb6ad313bbed36bb7ead5fa3a3e38938503142c72cba4f"}, + {url = "https://files.pythonhosted.org/packages/2b/d4/9165cf113f9b86ce55e36f36bc6cd9e0c5601b0ade02741b2ead8b5dc254/msgpack-1.0.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a2b031c2e9b9af485d5e3c4520f4220d74f4d222a5b8dc8c1a3ab9448ca79c57"}, + {url = "https://files.pythonhosted.org/packages/2c/e9/c79ecc36cfa34d850a01773565e0fccafd69efff07172028c3a5f758b83f/msgpack-1.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b2de4c1c0538dcb7010902a2b97f4e00fc4ddf2c8cda9749af0e594d3b7fa3d7"}, + {url = "https://files.pythonhosted.org/packages/2f/21/e488871f8e498efe14821b0c870eb95af52cfafb9b8dd41d83fad85b383b/msgpack-1.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48296af57cdb1d885843afd73c4656be5c76c0c6328db3440c9601a98f303d87"}, + {url = "https://files.pythonhosted.org/packages/33/0a/aa7b53ae17cf1dc1c352d705ab3162fc572c55048cc3177c1a88009c47fd/msgpack-1.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ab2f3331cb1b54165976a9d976cb251a83183631c88076613c6c780f0d6e45a"}, + {url = "https://files.pythonhosted.org/packages/33/52/099f0dde1283bac7bf267ab941dfa3b7c89ee701e4252973f8d3c10e68d6/msgpack-1.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:379026812e49258016dd84ad79ac8446922234d498058ae1d415f04b522d5b2d"}, + {url = "https://files.pythonhosted.org/packages/34/3c/34e94b091b3fdf941dbce5bc619e2fa5488d49fdf00944b50f5a1d6e1871/msgpack-1.0.5-cp36-cp36m-win32.whl", hash = "sha256:b5ef2f015b95f912c2fcab19c36814963b5463f1fb9049846994b007962743e9"}, + {url = "https://files.pythonhosted.org/packages/3c/e5/3d436bed11849ba05d777ed3fd1a0440170bad460335ea541dd6946047ed/msgpack-1.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:4867aa2df9e2a5fa5f76d7d5565d25ec76e84c106b55509e78c1ede0f152659a"}, + {url = "https://files.pythonhosted.org/packages/3e/80/bc7fdb75a35bf32c7c529c247dcadfd0502aac2309e207a89b0be6fe42ea/msgpack-1.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cb47c21a8a65b165ce29f2bec852790cbc04936f502966768e4aae9fa763cb7"}, + {url = "https://files.pythonhosted.org/packages/43/87/6507d56f62b958d822ae4ffe1c4507ed7d3cf37ad61114665816adcf4adc/msgpack-1.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a740fa0e4087a734455f0fc3abf5e746004c9da72fbd541e9b113013c8dc3282"}, + {url = "https://files.pythonhosted.org/packages/45/85/6b55b0cabad846d3e730226a897f878f8f63ee505668bb6c55a697b0bfb0/msgpack-1.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bf22a83f973b50f9d38e55c6aade04c41ddda19b00c4ebc558930d78eecc64ed"}, + {url = "https://files.pythonhosted.org/packages/45/e1/6408389bd2cf0c339ea317926beb64d100f60bc8d236ac59f1c1162be2e4/msgpack-1.0.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1d46dfe3832660f53b13b925d4e0fa1432b00f5f7210eb3ad3bb9a13c6204a6"}, + {url = "https://files.pythonhosted.org/packages/49/57/a28120d82f8e77622a1e1efc652389c71145f6b89b47b39814a7c6038373/msgpack-1.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57e1f3528bd95cc44684beda696f74d3aaa8a5e58c816214b9046512240ef437"}, + {url = "https://files.pythonhosted.org/packages/4b/3d/cc5eb6d69e0ecde80a78cc42f48579971ec333e509d56a4a6de1a2c40ba2/msgpack-1.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:266fa4202c0eb94d26822d9bfd7af25d1e2c088927fe8de9033d929dd5ba24c5"}, + {url = "https://files.pythonhosted.org/packages/56/50/bfcc0fad07067b6f1b09d940272ec749d5fe82570d938c2348c3ad0babf7/msgpack-1.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:821c7e677cc6acf0fd3f7ac664c98803827ae6de594a9f99563e48c5a2f27eb0"}, + {url = "https://files.pythonhosted.org/packages/59/67/f992ada3b42889f1b984e5651d63ea21ca3a92049cff6d75fe0a4a63e422/msgpack-1.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e42b9594cc3bf4d838d67d6ed62b9e59e201862a25e9a157019e171fbe672dd3"}, + {url = "https://files.pythonhosted.org/packages/60/bc/af94acdebc26b8d92d5673d20529438aa225698dc23338fb43c875c8968e/msgpack-1.0.5-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:f933bbda5a3ee63b8834179096923b094b76f0c7a73c1cfe8f07ad608c58844b"}, + {url = "https://files.pythonhosted.org/packages/62/57/170af6c6fccd2d950ea01e1faa58cae9643226fa8705baded11eca3aa8b5/msgpack-1.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c05a4a96585525916b109bb85f8cb6511db1c6f5b9d9cbcbc940dc6b4be944b"}, + {url = "https://files.pythonhosted.org/packages/62/5c/9c7fed4ca0235a2d7b8d15b4047c328976b97d2b227719e54cad1e47c244/msgpack-1.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef8108f8dedf204bb7b42994abf93882da1159728a2d4c5e82012edd92c9da9f"}, + {url = "https://files.pythonhosted.org/packages/67/f8/e3ab674f4a945308362e9342297fe6b35a89dd0f648aa325aabffa5dc210/msgpack-1.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3055b0455e45810820db1f29d900bf39466df96ddca11dfa6d074fa47054376d"}, + {url = "https://files.pythonhosted.org/packages/6b/6d/de239d77d347f1990c41b4800075a15e06f748186dd120166270dd071734/msgpack-1.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28592e20bbb1620848256ebc105fc420436af59515793ed27d5c77a217477705"}, + {url = "https://files.pythonhosted.org/packages/6b/79/0dec8f035160464ca88b221cc79691a71cf88dc25207c17f1d918b2c7bb0/msgpack-1.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c4c68d87497f66f96d50142a2b73b97972130d93677ce930718f68828b382e2"}, + {url = "https://files.pythonhosted.org/packages/6c/fa/3ca00fb1e53bcacf8c186fa6aff2d2086862b12e289bcf38227d9d40bd86/msgpack-1.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:06f5174b5f8ed0ed919da0e62cbd4ffde676a374aba4020034da05fab67b9164"}, + {url = "https://files.pythonhosted.org/packages/6c/fe/8a7747ca57074307a2e8f1de58441952a9dbdf9e8a8e5873d53a5ce0835c/msgpack-1.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5494ea30d517a3576749cad32fa27f7585c65f5f38309c88c6d137877fa28a5a"}, + {url = "https://files.pythonhosted.org/packages/72/ac/2eda5af7cd1450c52d031e48c76b280eac5bb2e588678876612f95be34ab/msgpack-1.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e57916ef1bd0fee4f21c4600e9d1da352d8816b52a599c46460e93a6e9f17086"}, + {url = "https://files.pythonhosted.org/packages/73/99/f338ce8b69e934c04e5d9187f85de1ae395882cd56e7deb48e78a1749af8/msgpack-1.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdc793c50be3f01106245a61b739328f7dccc2c648b501e237f0699fe1395b81"}, + {url = "https://files.pythonhosted.org/packages/7b/e9/b47f9e93fc381885624c40cbbbd0480b18ae11ca588162fe724d43495372/msgpack-1.0.5-cp310-cp310-win32.whl", hash = "sha256:382b2c77589331f2cb80b67cc058c00f225e19827dbc818d700f61513ab47bea"}, + {url = "https://files.pythonhosted.org/packages/7e/1c/9d0fd241a4e88e1cd2f5babea4a27ac25b1b86dbbc05fa10741e82079a93/msgpack-1.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe5c63197c55bce6385d9aee16c4d0641684628f63ace85f73571e65ad1c1e8d"}, + {url = "https://files.pythonhosted.org/packages/80/f0/c1fadb4e4a38fda19e35b1b6f887d72cc9c57778af43b53f64a8cd62e922/msgpack-1.0.5-cp38-cp38-win32.whl", hash = "sha256:1c0f7c47f0087ffda62961d425e4407961a7ffd2aa004c81b9c07d9269512f6e"}, + {url = "https://files.pythonhosted.org/packages/95/c9/560c3203c4327881c9f2de26c42dacdd9567bfe7fa43458e2a680c4bdcaf/msgpack-1.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1967f6129fc50a43bfe0951c35acbb729be89a55d849fab7686004da85103f1c"}, + {url = "https://files.pythonhosted.org/packages/9a/0b/ea8a49d24654f9e8604ea78b80a4d7b0cc31817d8fb6987001223ae7feaf/msgpack-1.0.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:36961b0568c36027c76e2ae3ca1132e35123dcec0706c4b7992683cc26c1320c"}, + {url = "https://files.pythonhosted.org/packages/9f/4a/36d936e54cf71e23ad276564465f6a54fb129e3d61520b76e13e0bb29167/msgpack-1.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:525228efd79bb831cf6830a732e2e80bc1b05436b086d4264814b4b2955b2fa9"}, + {url = "https://files.pythonhosted.org/packages/a2/e0/f3d5dd7809cf5728bb1bae683032ce50547d009be6551054815a8bf2a2da/msgpack-1.0.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4c075728a1095efd0634a7dccb06204919a2f67d1893b6aa8e00497258bf926c"}, + {url = "https://files.pythonhosted.org/packages/ab/ff/ca74e519c47139b6c08fb21db5ead2bd2eed6cb1225f9be69390cdb48182/msgpack-1.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:586d0d636f9a628ddc6a17bfd45aa5b5efaf1606d2b60fa5d87b8986326e933f"}, + {url = "https://files.pythonhosted.org/packages/b8/bc/1d5fe4732dc78ff86aaf677596da08f0ae736e60ca8ab49c1f1c7366cb1a/msgpack-1.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed40e926fa2f297e8a653c954b732f125ef97bdd4c889f243182299de27e2aa9"}, + {url = "https://files.pythonhosted.org/packages/bf/68/032e62ad44f92ba6a4ae7c45054843cdec7f0c405ecdfd166f25123b0c47/msgpack-1.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18334484eafc2b1aa47a6d42427da7fa8f2ab3d60b674120bce7a895a0a85bdd"}, + {url = "https://files.pythonhosted.org/packages/c1/57/01f2d8805160f559ec21d095fc7576a26fbaed2475af24ce4a135c380c14/msgpack-1.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e6ca5d5699bcd89ae605c150aee83b5321f2115695e741b99618f4856c50898"}, + {url = "https://files.pythonhosted.org/packages/c2/3b/70d1eaaafb451679663a72164c46fadfb93f59c90f584dcd77289f90e4c5/msgpack-1.0.5-cp36-cp36m-win_amd64.whl", hash = "sha256:288e32b47e67f7b171f86b030e527e302c91bd3f40fd9033483f2cacc37f327a"}, + {url = "https://files.pythonhosted.org/packages/c5/c1/1b591574ba71481fbf38359a8fca5108e4ad130a6dbb9b2acb3e9277d0fe/msgpack-1.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:137850656634abddfb88236008339fdaba3178f4751b28f270d2ebe77a563b6c"}, + {url = "https://files.pythonhosted.org/packages/ce/b8/89cb1809b076a4651169851aa1f98128b75cbfe14034b914c9040b13c4cf/msgpack-1.0.5-cp37-cp37m-win32.whl", hash = "sha256:cb5aaa8c17760909ec6cb15e744c3ebc2ca8918e727216e79607b7bbce9c8f77"}, + {url = "https://files.pythonhosted.org/packages/d3/32/9b7a2dba9485dd7d201e4e00638fbf86e0d535a91653889c5b4dc813efdf/msgpack-1.0.5-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:366c9a7b9057e1547f4ad51d8facad8b406bab69c7d72c0eb6f529cf76d4b85f"}, + {url = "https://files.pythonhosted.org/packages/da/46/855bdcbf004fd87b6a4451e8dcd61329439dcd9039887f71ca5085769216/msgpack-1.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:bae7de2026cbfe3782c8b78b0db9cbfc5455e079f1937cb0ab8d133496ac55e1"}, + {url = "https://files.pythonhosted.org/packages/dc/a1/eba11a0d4b764bc62966a565b470f8c6f38242723ba3057e9b5098678c30/msgpack-1.0.5.tar.gz", hash = "sha256:c075544284eadc5cddc70f4757331d99dcbc16b2bbd4849d15f8aae4cf36d31c"}, + {url = "https://files.pythonhosted.org/packages/e8/1f/be19c9c9cfdcc2ae8ee8c65dbe5f281cc1f3331f9b9523735f39b090b448/msgpack-1.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:ab31e908d8424d55601ad7075e471b7d0140d4d3dd3272daf39c5c19d936bd82"}, + {url = "https://files.pythonhosted.org/packages/e8/60/78906f564804aae23eb1102eca8b8830f1e08a649c179774c05fa7dc0aad/msgpack-1.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:17358523b85973e5f242ad74aa4712b7ee560715562554aa2134d96e7aa4cbbf"}, + {url = "https://files.pythonhosted.org/packages/e9/f1/45b73a9e97f702bcb5f51569b93990e456bc969363e55122374c22ed7d24/msgpack-1.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a61215eac016f391129a013c9e46f3ab308db5f5ec9f25811e811f96962599a8"}, + {url = "https://files.pythonhosted.org/packages/ef/13/c110d89d5079169354394dc226e6f84d818722939bc1fe3f9c25f982e903/msgpack-1.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1835c84d65f46900920b3708f5ba829fb19b1096c1800ad60bae8418652a951d"}, + {url = "https://files.pythonhosted.org/packages/f1/1f/cc3e8274934c8323f6106dae22cba8bad413166f4efb3819573de58c215c/msgpack-1.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:332360ff25469c346a1c5e47cbe2a725517919892eda5cfaffe6046656f0b7bb"}, + {url = "https://files.pythonhosted.org/packages/f2/da/770118f8d48e11cc9a2c7cb60d7d3c8016266526bd42c6ff5bd21013d099/msgpack-1.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4f8d8b3bf1ff2672567d6b5c725a1b347fe838b912772aa8ae2bf70338d5a198"}, + {url = "https://files.pythonhosted.org/packages/f5/80/ef9c31210ac580163c0de2db4fb3179c6a3f1228c18fd366280e01d9e5d2/msgpack-1.0.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f837b93669ce4336e24d08286c38761132bc7ab29782727f8557e1eb21b2080"}, +] +"nodeenv 1.8.0" = [ + {url = "https://files.pythonhosted.org/packages/1a/e6/6d2ead760a9ddb35e65740fd5a57e46aadd7b0c49861ab24f94812797a1c/nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, + {url = "https://files.pythonhosted.org/packages/48/92/8e83a37d3f4e73c157f9fcf9fb98ca39bd94701a469dc093b34dca31df65/nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, +] +"packaging 23.1" = [ + {url = "https://files.pythonhosted.org/packages/ab/c3/57f0601a2d4fe15de7a553c00adbc901425661bf048f2a22dfc500caf121/packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, + {url = "https://files.pythonhosted.org/packages/b9/6c/7c6658d258d7971c5eb0d9b69fa9265879ec9a9158031206d47800ae2213/packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, +] +"pip 23.2" = [ + {url = "https://files.pythonhosted.org/packages/02/65/f15431ddee78562355ccb39097bf9160a1689f2db40dc418754be98806a1/pip-23.2-py3-none-any.whl", hash = "sha256:78e5353a9dda374b462f2054f83a7b63f3f065c98236a68361845c1b0ee7e35f"}, + {url = "https://files.pythonhosted.org/packages/3d/ab/21fa8d1ecf5648559f056fda732b0f9fca0585eb2688252e67f70e74deaf/pip-23.2.tar.gz", hash = "sha256:a160a170f3331d9ca1a0247eb1cd79c758879f1f81158f9cd05bbb5df80bea5c"}, +] +"pip-tools 7.0.0" = [ + {url = "https://files.pythonhosted.org/packages/65/36/0447d5998a7cf8d017f0ed2361fd97cf68373ee3b4a18b646adbf8704fe3/pip_tools-7.0.0-py3-none-any.whl", hash = "sha256:ae185db747195c8ed011866c366279cbb64f7f8c1528e7a828f515bd2bb0b31b"}, + {url = "https://files.pythonhosted.org/packages/dd/08/76a220d1a5804bf0ad950ef3fd4894606281fc1fe674893deff795be7d91/pip-tools-7.0.0.tar.gz", hash = "sha256:6a2308712727c86cc8a6cedc0e6ba01232a337c706d63926d3789462ad083d06"}, +] +"platformdirs 3.9.1" = [ + {url = "https://files.pythonhosted.org/packages/6d/a7/47b7088a28c8fe5775eb15281bf44d39facdbe4bc011a95ccb89390c2db9/platformdirs-3.9.1-py3-none-any.whl", hash = "sha256:ad8291ae0ae5072f66c16945166cb11c63394c7a3ad1b1bc9828ca3162da8c2f"}, + {url = "https://files.pythonhosted.org/packages/a1/70/c1d14c0c58d975f06a449a403fac69d3c9c6e8ae2a529f387d77c29c2e56/platformdirs-3.9.1.tar.gz", hash = "sha256:1b42b450ad933e981d56e59f1b97495428c9bd60698baab9f3eb3d00d5822421"}, +] +"pluggy 1.2.0" = [ + {url = "https://files.pythonhosted.org/packages/51/32/4a79112b8b87b21450b066e102d6608907f4c885ed7b04c3fdb085d4d6ae/pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, + {url = "https://files.pythonhosted.org/packages/8a/42/8f2833655a29c4e9cb52ee8a2be04ceac61bcff4a680fb338cbd3d1e322d/pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, +] +"pre-commit 3.3.3" = [ + {url = "https://files.pythonhosted.org/packages/35/0e/564c71fe3cdf59a4acaaccaea354d066e5d9044eba564dac070bb2075432/pre_commit-3.3.3.tar.gz", hash = "sha256:a2256f489cd913d575c145132ae196fe335da32d91a8294b7afe6622335dd023"}, + {url = "https://files.pythonhosted.org/packages/e3/b7/1d145c985d8be9729672a45b8b8113030ad60dff45dec592efc4e5f5897a/pre_commit-3.3.3-py2.py3-none-any.whl", hash = "sha256:10badb65d6a38caff29703362271d7dca483d01da88f9d7e05d0b97171c136cb"}, +] +"py4j 0.10.9.5" = [ + {url = "https://files.pythonhosted.org/packages/86/ec/60880978512d5569ca4bf32b3b4d7776a528ecf4bca4523936c98c92a3c8/py4j-0.10.9.5-py2.py3-none-any.whl", hash = "sha256:52d171a6a2b031d8a5d1de6efe451cf4f5baff1a2819aabc3741c8406539ba04"}, + {url = "https://files.pythonhosted.org/packages/ce/1f/b00295b6da3bd2f050912b9f71fdb436ae8f1601cf161365937d8553e24b/py4j-0.10.9.5.tar.gz", hash = "sha256:276a4a3c5a2154df1860ef3303a927460e02e97b047dc0a47c1c3fb8cce34db6"}, +] +"pydantic 1.10.11" = [ + {url = "https://files.pythonhosted.org/packages/04/70/8314870d16db3984417dc74404eb6518a8ae6324960da56505047accf6c3/pydantic-1.10.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e6cbfbd010b14c8a905a7b10f9fe090068d1744d46f9e0c021db28daeb8b6de1"}, + {url = "https://files.pythonhosted.org/packages/05/2c/09a35e6c16e206c80906ce93c0dd1e86d8b9738ea05eb82fe38c8f0cfab1/pydantic-1.10.11-cp310-cp310-win_amd64.whl", hash = "sha256:c3339a46bbe6013ef7bdd2844679bfe500347ac5742cd4019a88312aa58a9847"}, + {url = "https://files.pythonhosted.org/packages/11/54/a010e20824e6bfe04902830f92923997e8000d64d14099999edee9076924/pydantic-1.10.11-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:41e0bb6efe86281623abbeeb0be64eab740c865388ee934cd3e6a358784aca6e"}, + {url = "https://files.pythonhosted.org/packages/22/aa/24ef442d21b1ddafb52fae1e50ebe76cfaf36cb0c58a046fa6eb1d87310d/pydantic-1.10.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:469adf96c8e2c2bbfa655fc7735a2a82f4c543d9fee97bd113a7fb509bf5e622"}, + {url = "https://files.pythonhosted.org/packages/3c/c0/57fb46d928d8f76736d99d1d2a3c00483e0f4029cfac6cd57584a3f7379b/pydantic-1.10.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc64eab9b19cd794a380179ac0e6752335e9555d214cfcb755820333c0784cb3"}, + {url = "https://files.pythonhosted.org/packages/45/7f/ba7118bbc361bbf10e51f3b90a2072c26c29a20491ab7a31b30ee499e8ab/pydantic-1.10.11-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ef55392ec4bb5721f4ded1096241e4b7151ba6d50a50a80a2526c854f42e6a2f"}, + {url = "https://files.pythonhosted.org/packages/46/44/2390ef51d3fc48b4abb7d8928feaa9d43ff66df2c278476862c7754775d4/pydantic-1.10.11-cp37-cp37m-win_amd64.whl", hash = "sha256:7522a7666157aa22b812ce14c827574ddccc94f361237ca6ea8bb0d5c38f1629"}, + {url = "https://files.pythonhosted.org/packages/4a/48/9dd66a938b0c5bb439ade89f3aac37a580736a8acf2da2c3e14ecdcd34f0/pydantic-1.10.11-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e297897eb4bebde985f72a46a7552a7556a3dd11e7f76acda0c1093e3dbcf216"}, + {url = "https://files.pythonhosted.org/packages/4c/4a/3865d5293f04cff244bfcaeb1b14b651ca3bed8ba7dd61cf74d9127a77e9/pydantic-1.10.11-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:373c0840f5c2b5b1ccadd9286782852b901055998136287828731868027a724f"}, + {url = "https://files.pythonhosted.org/packages/4e/d7/04c964cda3b5e2c05d40d89fffa4449512c757476c9dd6aee9154a2e2603/pydantic-1.10.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe429898f2c9dd209bd0632a606bddc06f8bce081bbd03d1c775a45886e2c1cb"}, + {url = "https://files.pythonhosted.org/packages/63/cf/023a359e3c86a909494d6729e9d35304449de8231a2d8b3132bc93fb5b0a/pydantic-1.10.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8dc77064471780262b6a68fe67e013298d130414d5aaf9b562c33987dbd2cf4f"}, + {url = "https://files.pythonhosted.org/packages/65/d3/8ea06a592f4c218d3079ddb6d267015e6635c11ea4b282c2f5a9b62ca60b/pydantic-1.10.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abade85268cc92dff86d6effcd917893130f0ff516f3d637f50dadc22ae93999"}, + {url = "https://files.pythonhosted.org/packages/6a/19/af6ac6f22f9a2a3866fc5a726dca0b7d524e1660821388dc99d56764e6df/pydantic-1.10.11-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:331c031ba1554b974c98679bd0780d89670d6fd6f53f5d70b10bdc9addee1713"}, + {url = "https://files.pythonhosted.org/packages/79/3e/6b4d0fb2174beceac9a991ba8e67158b45c35faca9ea4545ae32d47096cd/pydantic-1.10.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a6c098d4ab5e2d5b3984d3cb2527e2d6099d3de85630c8934efcfdc348a9760e"}, + {url = "https://files.pythonhosted.org/packages/87/18/76af036e978c00d4b1525eeaa84e67a8ea93429ceb1adaa67491af44e214/pydantic-1.10.11-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8268a735a14c308923e8958363e3a3404f6834bb98c11f5ab43251a4e410170c"}, + {url = "https://files.pythonhosted.org/packages/90/6c/ef2c34321f035197621171d5cf243aedc43db69148b07308d95ca27bc036/pydantic-1.10.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ff44c5e89315b15ff1f7fdaf9853770b810936d6b01a7bcecaa227d2f8fe444f"}, + {url = "https://files.pythonhosted.org/packages/93/4c/c1d4530819924e77285a32768629fab5d40bbbbdba2f78a1b1fc0649525a/pydantic-1.10.11-cp311-cp311-win_amd64.whl", hash = "sha256:4400015f15c9b464c9db2d5d951b6a780102cfa5870f2c036d37c23b56f7fc1b"}, + {url = "https://files.pythonhosted.org/packages/93/63/c2b91f7482bf8e5b7166dba6558287d906587f269158b8fad1146031fdc3/pydantic-1.10.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a451ccab49971af043ec4e0d207cbc8cbe53dbf148ef9f19599024076fe9c25b"}, + {url = "https://files.pythonhosted.org/packages/96/84/e6d9843d3d865efa54f7080183cbbe8f337a08c469b44a6adf064e45005c/pydantic-1.10.11-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e9baf78b31da2dc3d3f346ef18e58ec5f12f5aaa17ac517e2ffd026a92a87588"}, + {url = "https://files.pythonhosted.org/packages/af/c3/216d527637fb6de1d2c08f870a094f239f8e5a127b9693835cedc29616f7/pydantic-1.10.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b02d24f7b2b365fed586ed73582c20f353a4c50e4be9ba2c57ab96f8091ddae"}, + {url = "https://files.pythonhosted.org/packages/b6/6a/ef5f54d0acdc34dc05d8e72bdf1673d67e0e05b6b7743ece209c76e1f45c/pydantic-1.10.11-cp38-cp38-win_amd64.whl", hash = "sha256:265a60da42f9f27e0b1014eab8acd3e53bd0bad5c5b4884e98a55f8f596b2c19"}, + {url = "https://files.pythonhosted.org/packages/b6/8e/7dd215f91528487535e7aa048e4092c20ecd0168df958e58809e2235cece/pydantic-1.10.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16928fdc9cb273c6af00d9d5045434c39afba5f42325fb990add2c241402d151"}, + {url = "https://files.pythonhosted.org/packages/bf/43/3fcc2d7fd6f1352677a9ea47123004da5f68a903fae48fab40f03ca2e0b1/pydantic-1.10.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0588788a9a85f3e5e9ebca14211a496409cb3deca5b6971ff37c556d581854e7"}, + {url = "https://files.pythonhosted.org/packages/ca/79/8b33ca15336599bad10bd15820182fbf4045e7a2f677b7fbf89794785f60/pydantic-1.10.11-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d7781f1d13b19700b7949c5a639c764a077cbbdd4322ed505b449d3ca8edcb36"}, + {url = "https://files.pythonhosted.org/packages/ce/bd/beabbb503f06ebe9140752a4cd80a6d3fd8895764a5e34083d9c97ec5f68/pydantic-1.10.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e9738b0f2e6c70f44ee0de53f2089d6002b10c33264abee07bdb5c7f03038303"}, + {url = "https://files.pythonhosted.org/packages/cf/01/e8a380dc6e92a76113f034c58c9ffdbd115753e9b944ddf5d2dbe862f248/pydantic-1.10.11.tar.gz", hash = "sha256:f66d479cf7eb331372c470614be6511eae96f1f120344c25f3f9bb59fb1b5528"}, + {url = "https://files.pythonhosted.org/packages/d0/30/b295a270bef59eda75d5862bba8a6a27fed9a2b91000cccd5dfddb91ee1c/pydantic-1.10.11-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d185819a7a059550ecb85d5134e7d40f2565f3dd94cfd870132c5f91a89cf58c"}, + {url = "https://files.pythonhosted.org/packages/d0/ab/df82ca386c559accacaaf082dc5d178b9257ace0d4f83b09f4a2066a8a61/pydantic-1.10.11-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2417de68290434461a266271fc57274a138510dca19982336639484c73a07af6"}, + {url = "https://files.pythonhosted.org/packages/d3/b4/44df92e63c99834a07e2ae0cb7c9c4464b0e3a0539d25f68fc9811e9b12e/pydantic-1.10.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f34739a89260dfa420aa3cbd069fbcc794b25bbe5c0a214f8fb29e363484b66"}, + {url = "https://files.pythonhosted.org/packages/da/6f/cbd0b6d75c5c35234fdf6feb0fcb26812d87be9b2eab7b2d1596f30f52c0/pydantic-1.10.11-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:44e51ba599c3ef227e168424e220cd3e544288c57829520dc90ea9cb190c3248"}, + {url = "https://files.pythonhosted.org/packages/dc/f0/604245a9bcaa982c86caeaa98fdeabddc1ddea097ae77753a6f9d6b08e12/pydantic-1.10.11-py3-none-any.whl", hash = "sha256:008c5e266c8aada206d0627a011504e14268a62091450210eda7c07fabe6963e"}, + {url = "https://files.pythonhosted.org/packages/e6/c7/99b3678f019a1b90e3242a8b7006b5181f6739d1791caaaebe83f1aab5a9/pydantic-1.10.11-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:192c608ad002a748e4a0bed2ddbcd98f9b56df50a7c24d9a931a8c5dd053bd3d"}, + {url = "https://files.pythonhosted.org/packages/ed/a9/9f6fa825214510f79ce3fae718db66c9c55cba8d087e2e4400e4a634173a/pydantic-1.10.11-cp39-cp39-win_amd64.whl", hash = "sha256:1954f8778489a04b245a1e7b8b22a9d3ea8ef49337285693cf6959e4b757535e"}, + {url = "https://files.pythonhosted.org/packages/f1/79/e28edde4bca34e08958d02f14e45805c2b18110aa720cdb900022fc210f7/pydantic-1.10.11-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:174899023337b9fc685ac8adaa7b047050616136ccd30e9070627c1aaab53a13"}, + {url = "https://files.pythonhosted.org/packages/f9/ba/19c03d48e76940b4970e9b9ac626d104e69f9ee2d67ec778f1142ce2f559/pydantic-1.10.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:08a6c32e1c3809fbc49debb96bf833164f3438b3696abf0fbeceb417d123e6eb"}, + {url = "https://files.pythonhosted.org/packages/fe/1b/266ef0137e71d3405762521db8c0a4a07d5883245b7abac9e800fef4729b/pydantic-1.10.11-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:787cf23e5a0cde753f2eabac1b2e73ae3844eb873fd1f5bdbff3048d8dbb7604"}, +] +"pydruid 0.6.5" = [ + {url = "https://files.pythonhosted.org/packages/9a/c1/abb96d65c52bcf3596bef0eb6a793c457bd22c024f32b20a53acde9f0864/pydruid-0.6.5.tar.gz", hash = "sha256:30b6efa722234183239296bf8e6d41aba7eea0eec3d68233ce6c413986864fea"}, +] +"pyfakefs 5.2.3" = [ + {url = "https://files.pythonhosted.org/packages/d6/15/d34e974624dec41d6bcee788b78334d7a80650ed5c25cbee8f2f98af7a36/pyfakefs-5.2.3-py3-none-any.whl", hash = "sha256:101a91d8e454934fe2435392c38d505beacfc27dad71a0ad2a6215d0b750f2f1"}, + {url = "https://files.pythonhosted.org/packages/e4/d9/4d7b8d1a0b0fc18a371ee24d19a61117d721df966ddd7292997e00b6c1f5/pyfakefs-5.2.3.tar.gz", hash = "sha256:f4d677645e44c56fd47d579c7586ff0daef1546d3100df2af50969f794368fc6"}, +] +"pygments 2.15.1" = [ + {url = "https://files.pythonhosted.org/packages/34/a7/37c8d68532ba71549db4212cb036dbd6161b40e463aba336770e80c72f84/Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"}, + {url = "https://files.pythonhosted.org/packages/89/6b/2114e54b290824197006e41be3f9bbe1a26e9c39d1f5fa20a6d62945a0b3/Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"}, +] +"pylint 2.17.4" = [ + {url = "https://files.pythonhosted.org/packages/04/4c/3f7d42a1378c40813772bc5f25184144da09f00ffbe3f60ae985ffa7e10f/pylint-2.17.4-py3-none-any.whl", hash = "sha256:7a1145fb08c251bdb5cca11739722ce64a63db479283d10ce718b2460e54123c"}, + {url = "https://files.pythonhosted.org/packages/7e/d4/aba77d10841710fea016422f419dfe501dee05fa0fc3898dc048f7bf3f60/pylint-2.17.4.tar.gz", hash = "sha256:5dcf1d9e19f41f38e4e85d10f511e5b9c35e1aa74251bf95cdd8cb23584e2db1"}, +] +"pyproject-hooks 1.0.0" = [ + {url = "https://files.pythonhosted.org/packages/25/c1/374304b8407d3818f7025457b7366c8e07768377ce12edfe2aa58aa0f64c/pyproject_hooks-1.0.0.tar.gz", hash = "sha256:f271b298b97f5955d53fb12b72c1fb1948c22c1a6b70b315c54cedaca0264ef5"}, + {url = "https://files.pythonhosted.org/packages/d5/ea/9ae603de7fbb3df820b23a70f6aff92bf8c7770043254ad8d2dc9d6bcba4/pyproject_hooks-1.0.0-py3-none-any.whl", hash = "sha256:283c11acd6b928d2f6a7c73fa0d01cb2bdc5f07c57a2eeb6e83d5e56b97976f8"}, +] +"pyspark 3.3.2" = [ + {url = "https://files.pythonhosted.org/packages/6d/08/87b404b8b3255d46caf0ecdccf871d501a2b58da9b844d3f9710ce9d4d53/pyspark-3.3.2.tar.gz", hash = "sha256:0dfd5db4300c1f6cc9c16d8dbdfb82d881b4b172984da71344ede1a9d4893da8"}, +] +"pytest 7.4.0" = [ + {url = "https://files.pythonhosted.org/packages/33/b2/741130cbcf2bbfa852ed95a60dc311c9e232c7ed25bac3d9b8880a8df4ae/pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, + {url = "https://files.pythonhosted.org/packages/a7/f3/dadfbdbf6b6c8b5bd02adb1e08bc9fbb45ba51c68b0893fa536378cdf485/pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, +] +"pytest-asyncio 0.15.1" = [ + {url = "https://files.pythonhosted.org/packages/c3/8a/a3454b14124af9858fc2936f85abc4972f26a2bbd4ffd4251aad7ec17dd7/pytest_asyncio-0.15.1-py3-none-any.whl", hash = "sha256:3042bcdf1c5d978f6b74d96a151c4cfb9dcece65006198389ccd7e6c60eb1eea"}, + {url = "https://files.pythonhosted.org/packages/de/c1/b2b0119e30f61f7ec8b44f129f6fde46a1a7329de17110f124639aa8896b/pytest-asyncio-0.15.1.tar.gz", hash = "sha256:2564ceb9612bbd560d19ca4b41347b54e7835c2f792c504f698e05395ed63f6f"}, +] +"pytest-cov 4.1.0" = [ + {url = "https://files.pythonhosted.org/packages/7a/15/da3df99fd551507694a9b01f512a2f6cf1254f33601605843c3775f39460/pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, + {url = "https://files.pythonhosted.org/packages/a7/4b/8b78d126e275efa2379b1c2e09dc52cf70df16fc3b90613ef82531499d73/pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, +] +"pytest-integration 0.2.2" = [ + {url = "https://files.pythonhosted.org/packages/3f/68/d05929a7b205e0ac1b06c2895356eac6b127ac2537c16e112f2a6e209101/pytest_integration-0.2.2.tar.gz", hash = "sha256:7630b2bb1a8d518168bae44d827c20c4f0c1bbc5a1d3e1014dc5624ccadcdbd1"}, + {url = "https://files.pythonhosted.org/packages/fc/e3/9eedec116e74c22687e55ed3143a223bae17dbd785eae92a968596ba14c8/pytest_integration-0.2.2-py3-none-any.whl", hash = "sha256:560b18c003cf6a3d6672878e826a823ea5f8d1d289dbe97546495040b2f0bd3d"}, +] +"pytest-mock 3.11.1" = [ + {url = "https://files.pythonhosted.org/packages/d8/2d/b3a811ec4fa24190a9ec5013e23c89421a7916167c6240c31fdc445f850c/pytest-mock-3.11.1.tar.gz", hash = "sha256:7f6b125602ac6d743e523ae0bfa71e1a697a2f5534064528c6ff84c2f7c2fc7f"}, + {url = "https://files.pythonhosted.org/packages/da/85/80ae98e019a429445bfb74e153d4cb47c3695e3e908515e95e95c18237e5/pytest_mock-3.11.1-py3-none-any.whl", hash = "sha256:21c279fff83d70763b05f8874cc9cfb3fcacd6d354247a976f9529d19f9acf39"}, +] +"python-dateutil 2.8.2" = [ + {url = "https://files.pythonhosted.org/packages/36/7a/87837f39d0296e723bb9b62bbb257d0355c7f6128853c78955f57342a56d/python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, + {url = "https://files.pythonhosted.org/packages/4c/c4/13b4776ea2d76c115c1d1b84579f3764ee6d57204f6be27119f13a61d0a9/python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, +] +"python-dotenv 0.19.2" = [ + {url = "https://files.pythonhosted.org/packages/0e/f1/0317f4b2c5284075a2154fe95539b43c0acecbcb86fe80fcb2645803edd9/python_dotenv-0.19.2-py2.py3-none-any.whl", hash = "sha256:32b2bdc1873fd3a3c346da1c6db83d0053c3c62f28f1f38516070c4c8971b1d3"}, + {url = "https://files.pythonhosted.org/packages/49/62/4f25667e10561303a34cb89e3187c35985c0889b99f6f1468aaf17fbb03e/python-dotenv-0.19.2.tar.gz", hash = "sha256:a5de49a31e953b45ff2d2fd434bbc2670e8db5273606c1e737cc6b93eff3655f"}, +] +"pyyaml 6.0" = [ + {url = "https://files.pythonhosted.org/packages/02/25/6ba9f6bb50a3d4fbe22c1a02554dc670682a07c8701d1716d19ddea2c940/PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {url = "https://files.pythonhosted.org/packages/08/f4/ffa743f860f34a5e8c60abaaa686f82c9ac7a2b50e5a1c3b1eb564d59159/PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {url = "https://files.pythonhosted.org/packages/0f/93/5f81d1925ce3b531f5ff215376445ec220887cd1c9a8bde23759554dbdfd/PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {url = "https://files.pythonhosted.org/packages/12/fc/a4d5a7554e0067677823f7265cb3ae22aed8a238560b5133b58cda252dad/PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {url = "https://files.pythonhosted.org/packages/21/67/b42191239c5650c9e419c4a08a7a022bbf1abf55b0391c380a72c3af5462/PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {url = "https://files.pythonhosted.org/packages/2e/b3/13dfd4eeb5e4b2d686b6d1822b40702e991bf3a4194ca5cbcce8d43749db/PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {url = "https://files.pythonhosted.org/packages/36/2b/61d51a2c4f25ef062ae3f74576b01638bebad5e045f747ff12643df63844/PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, + {url = "https://files.pythonhosted.org/packages/44/e5/4fea13230bcebf24b28c0efd774a2dd65a0937a2d39e94a4503438b078ed/PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {url = "https://files.pythonhosted.org/packages/4d/7d/c2ab8da648cd2b937de11fb35649b127adab4851cbeaf5fd9b60a2dab0f7/PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {url = "https://files.pythonhosted.org/packages/55/e3/507a92589994a5b3c3d7f2a7a066339d6ff61c5c839bae56f7eff03d9c7b/PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {url = "https://files.pythonhosted.org/packages/56/8f/e8b49ad21d26111493dc2d5cae4d7efbd0e2e065440665f5023515f87f64/PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, + {url = "https://files.pythonhosted.org/packages/59/00/30e33fcd2a4562cd40c49c7740881009240c5cbbc0e41ca79ca4bba7c24b/PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, + {url = "https://files.pythonhosted.org/packages/5e/f4/7b4bb01873be78fc9fde307f38f62e380b7111862c165372cf094ca2b093/PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {url = "https://files.pythonhosted.org/packages/63/6b/f5dc7942bac17192f4ef00b2d0cdd1ae45eea453d05c1944c0573debe945/PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {url = "https://files.pythonhosted.org/packages/67/d4/b95266228a25ef5bd70984c08b4efce2c035a4baa5ccafa827b266e3dc36/PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {url = "https://files.pythonhosted.org/packages/68/3f/c027422e49433239267c62323fbc6320d6ac8d7d50cf0cb2a376260dad5f/PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, + {url = "https://files.pythonhosted.org/packages/6c/3d/524c642f3db37e7e7ab8d13a3f8b0c72d04a619abc19100097d987378fc6/PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {url = "https://files.pythonhosted.org/packages/74/68/3c13deaa496c14a030c431b7b828d6b343f79eb241b4848c7918091a64a2/PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {url = "https://files.pythonhosted.org/packages/77/da/e845437ffe0dffae4e7562faf23a4f264d886431c5d2a2816c853288dc8e/PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {url = "https://files.pythonhosted.org/packages/7f/d9/6a0d14ac8d3b5605dc925d177c1d21ee9f0b7b39287799db1e50d197b2f4/PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, + {url = "https://files.pythonhosted.org/packages/81/59/561f7e46916b78f3c4cab8d0c307c81656f11e32c846c0c97fda0019ed76/PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {url = "https://files.pythonhosted.org/packages/89/26/0bfd7b756b34c68f8fd158b7bc762b6b1705fc1b3cebf4cdbb53fd9ea75b/PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {url = "https://files.pythonhosted.org/packages/91/49/d46d7b15cddfa98533e89f3832f391aedf7e31f37b4d4df3a7a7855a7073/PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {url = "https://files.pythonhosted.org/packages/9d/f6/7e91fbb58c9ee528759aea5892e062cccb426720c5830ddcce92eba00ff1/PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {url = "https://files.pythonhosted.org/packages/a4/ba/e508fc780e3c94c12753a54fe8f74de535741a10d33b29a576a9bec03500/PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {url = "https://files.pythonhosted.org/packages/a4/e6/4d7a01bc0730c8f958a62d6a4c4f3df23b6139ad68c132b168970d84f192/PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {url = "https://files.pythonhosted.org/packages/a8/32/1bbe38477fb23f1d83041fefeabf93ef1cd6f0efcf44c221519507315d92/PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {url = "https://files.pythonhosted.org/packages/a8/5b/c4d674846ea4b07ee239fbf6010bcc427c4e4552ba5655b446e36b9a40a7/PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {url = "https://files.pythonhosted.org/packages/b3/85/79b9e5b4e8d3c0ac657f4e8617713cca8408f6cdc65d2ee6554217cedff1/PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {url = "https://files.pythonhosted.org/packages/b7/09/2f6f4851bbca08642fef087bade095edc3c47f28d1e7bff6b20de5262a77/PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {url = "https://files.pythonhosted.org/packages/cb/5f/05dd91f5046e2256e35d885f3b8f0f280148568f08e1bf20421887523e9a/PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, + {url = "https://files.pythonhosted.org/packages/d1/c0/4fe04181b0210ee2647cfbb89ecd10a36eef89f10d8aca6a192c201bbe58/PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {url = "https://files.pythonhosted.org/packages/d7/42/7ad4b6d67a16229496d4f6e74201bdbebcf4bc1e87d5a70c9297d4961bd2/PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {url = "https://files.pythonhosted.org/packages/db/4e/74bc723f2d22677387ab90cd9139e62874d14211be7172ed8c9f9a7c81a9/PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {url = "https://files.pythonhosted.org/packages/df/75/ee0565bbf65133e5b6ffa154db43544af96ea4c42439e6b58c1e0eb44b4e/PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {url = "https://files.pythonhosted.org/packages/eb/5f/6e6fe6904e1a9c67bc2ca5629a69e7a5a0b17f079da838bab98a1e548b25/PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {url = "https://files.pythonhosted.org/packages/ef/ad/b443cce94539e57e1a745a845f95c100ad7b97593d7e104051e43f730ecd/PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {url = "https://files.pythonhosted.org/packages/f5/6f/b8b4515346af7c33d3b07cd8ca8ea0700ca72e8d7a750b2b87ac0268ca4e/PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {url = "https://files.pythonhosted.org/packages/f8/54/799b059314b13e1063473f76e908f44106014d18f54b16c83a16edccd5ec/PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, + {url = "https://files.pythonhosted.org/packages/fc/48/531ecd926fe0a374346dd811bf1eda59a95583595bb80eadad511f3269b8/PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, +] +"requests 2.29.0" = [ + {url = "https://files.pythonhosted.org/packages/4c/d2/70fc708727b62d55bc24e43cc85f073039023212d482553d853c44e57bdb/requests-2.29.0.tar.gz", hash = "sha256:f2e34a75f4749019bb0e3effb66683630e4ffeaf75819fb51bebef1bf5aef059"}, + {url = "https://files.pythonhosted.org/packages/cf/e1/2aa539876d9ed0ddc95882451deb57cfd7aa8dbf0b8dbce68e045549ba56/requests-2.29.0-py3-none-any.whl", hash = "sha256:e8f3c9be120d3333921d213eef078af392fba3933ab7ed2d1cba3b56f2568c3b"}, +] +"requests-mock 1.11.0" = [ + {url = "https://files.pythonhosted.org/packages/29/e8/ce73e8d1c7ec127cb3af61df3fd04df9a34eef34e511dc03c069748f773d/requests_mock-1.11.0-py2.py3-none-any.whl", hash = "sha256:f7fae383f228633f6bececebdab236c478ace2284d6292c6e7e2867b9ab74d15"}, + {url = "https://files.pythonhosted.org/packages/c8/7a/fa102fafdc8547ebd744d69d09f4bc314ead1a858fe59d9c2d32591c5f4e/requests-mock-1.11.0.tar.gz", hash = "sha256:ef10b572b489a5f28e09b708697208c4a3b2b89ef80a9f01584340ea357ec3c4"}, +] +"rich 13.4.2" = [ + {url = "https://files.pythonhosted.org/packages/e3/12/67d0098eb77005f5e068de639e6f4cfb8f24e6fcb0fd2037df0e1d538fee/rich-13.4.2.tar.gz", hash = "sha256:d653d6bccede5844304c605d5aac802c7cf9621efd700b46c7ec2b51ea914898"}, + {url = "https://files.pythonhosted.org/packages/fc/1e/482e5eec0b89b593e81d78f819a9412849814e22225842b598908e7ac560/rich-13.4.2-py3-none-any.whl", hash = "sha256:8f87bc7ee54675732fa66a05ebfe489e27264caeeff3728c945d25971b6485ec"}, +] +"setuptools 68.0.0" = [ + {url = "https://files.pythonhosted.org/packages/c7/42/be1c7bbdd83e1bfb160c94b9cafd8e25efc7400346cf7ccdbdb452c467fa/setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, + {url = "https://files.pythonhosted.org/packages/dc/98/5f896af066c128669229ff1aa81553ac14cfb3e5e74b6b44594132b8540e/setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, +] +"six 1.16.0" = [ + {url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, + {url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, +] +"sniffio 1.3.0" = [ + {url = "https://files.pythonhosted.org/packages/c3/a0/5dba8ed157b0136607c7f2151db695885606968d1fae123dc3391e0cfdbf/sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, + {url = "https://files.pythonhosted.org/packages/cd/50/d49c388cae4ec10e8109b1b833fd265511840706808576df3ada99ecb0ac/sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, +] +"sqlalchemy 1.4.41" = [ + {url = "https://files.pythonhosted.org/packages/05/f5/23735f8e87c4c66058b327773654930898cdb3e206a8ddb22aadc2e54cea/SQLAlchemy-1.4.41-cp36-cp36m-win32.whl", hash = "sha256:3e2ef592ac3693c65210f8b53d0edcf9f4405925adcfc031ff495e8d18169682"}, + {url = "https://files.pythonhosted.org/packages/07/0d/46d1a6c25fce13d2c6892e9a203d4baae3058cb04396915365d621965f95/SQLAlchemy-1.4.41-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:639e1ae8d48b3c86ffe59c0daa9a02e2bfe17ca3d2b41611b30a0073937d4497"}, + {url = "https://files.pythonhosted.org/packages/08/a8/8146793f1cbe0b7753463e885dd30ad2f647d700530625598355863397b5/SQLAlchemy-1.4.41-cp37-cp37m-win_amd64.whl", hash = "sha256:5323252be2bd261e0aa3f33cb3a64c45d76829989fa3ce90652838397d84197d"}, + {url = "https://files.pythonhosted.org/packages/10/60/e891b496ca0bbbabedcb387d43be52b6b59dfb902a0e2df26d1cc43caf4c/SQLAlchemy-1.4.41-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2d6495f84c4fd11584f34e62f9feec81bf373787b3942270487074e35cbe5330"}, + {url = "https://files.pythonhosted.org/packages/1b/82/53cc4c827ce330ce97767a3536e320e58f8803da3255ba4752ca20d8f376/SQLAlchemy-1.4.41-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:036d8472356e1d5f096c5e0e1a7e0f9182140ada3602f8fff6b7329e9e7cfbcd"}, + {url = "https://files.pythonhosted.org/packages/1d/46/208bb085d3405eaec7aa41e8b3eda0c3aa596169e0d31c7bcc75ad1b9abc/SQLAlchemy-1.4.41-cp37-cp37m-win32.whl", hash = "sha256:0005bd73026cd239fc1e8ccdf54db58b6193be9a02b3f0c5983808f84862c767"}, + {url = "https://files.pythonhosted.org/packages/37/b5/136c78031fb88f3f79fa1090c339f36a7b9bbb359651767b617f2bbf655a/SQLAlchemy-1.4.41-cp311-cp311-win_amd64.whl", hash = "sha256:d2e054aed4645f9b755db85bc69fc4ed2c9020c19c8027976f66576b906a74f1"}, + {url = "https://files.pythonhosted.org/packages/39/ec/02955ea76aca27cba7b280cea29f7952133f154b3a0be50281f125a4c753/SQLAlchemy-1.4.41-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:c23d64a0b28fc78c96289ffbd0d9d1abd48d267269b27f2d34e430ea73ce4b26"}, + {url = "https://files.pythonhosted.org/packages/42/8b/4ddf009cb17231471419d9e31dd03005c0b31f8a4e94a9cd1a0b4ade44d4/SQLAlchemy-1.4.41-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:4ba7e122510bbc07258dc42be6ed45997efdf38129bde3e3f12649be70683546"}, + {url = "https://files.pythonhosted.org/packages/5b/05/0344b99768d345cd92785949a3dac38bfb7059b3b4dc6ae1e55ea842c772/SQLAlchemy-1.4.41-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:361f6b5e3f659e3c56ea3518cf85fbdae1b9e788ade0219a67eeaaea8a4e4d2a"}, + {url = "https://files.pythonhosted.org/packages/5b/3d/4c6da7a76f850c55e9115d5bcf2f90509a8617f4e955d9bd82f23008e029/SQLAlchemy-1.4.41-cp38-cp38-win32.whl", hash = "sha256:58bb65b3274b0c8a02cea9f91d6f44d0da79abc993b33bdedbfec98c8440175a"}, + {url = "https://files.pythonhosted.org/packages/5c/0c/4256c722fc41e7f581776ac05af9b5db5c304c7888d625e47d079024c7b8/SQLAlchemy-1.4.41-cp38-cp38-win_amd64.whl", hash = "sha256:ce8feaa52c1640de9541eeaaa8b5fb632d9d66249c947bb0d89dd01f87c7c288"}, + {url = "https://files.pythonhosted.org/packages/67/a0/97da2cb07e013fd6c37fd896a86b374aa726e4161cafd57185e8418d59aa/SQLAlchemy-1.4.41.tar.gz", hash = "sha256:0292f70d1797e3c54e862e6f30ae474014648bc9c723e14a2fda730adb0a9791"}, + {url = "https://files.pythonhosted.org/packages/73/2e/d61aeec5580ae1841508c39ac63a9a8cfb8200d88f3d9b7d57607ab2f245/SQLAlchemy-1.4.41-cp39-cp39-win_amd64.whl", hash = "sha256:f5fa526d027d804b1f85cdda1eb091f70bde6fb7d87892f6dd5a48925bc88898"}, + {url = "https://files.pythonhosted.org/packages/79/5f/cf2664ea15b04cfacab5f9ed791741874c67d58f69ad86c22488bc53a2f0/SQLAlchemy-1.4.41-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:14576238a5f89bcf504c5f0a388d0ca78df61fb42cb2af0efe239dc965d4f5c9"}, + {url = "https://files.pythonhosted.org/packages/7e/7f/0693241547e0b8534600e831dfe0a8bbcb29a60c53925ed604a747a00bb8/SQLAlchemy-1.4.41-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e16c2be5cb19e2c08da7bd3a87fed2a0d4e90065ee553a940c4fc1a0fb1ab72b"}, + {url = "https://files.pythonhosted.org/packages/85/8a/83f1056449d819532c337a4a1b709a8e6291b9398340c0b2c00d5fdc7589/SQLAlchemy-1.4.41-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:05f0de3a1dc3810a776275763764bb0015a02ae0f698a794646ebc5fb06fad33"}, + {url = "https://files.pythonhosted.org/packages/93/0c/377daa276fa54ad65a6dbd0323285cf0892972fa88a4dbe17113ec440c32/SQLAlchemy-1.4.41-cp311-cp311-win32.whl", hash = "sha256:59bdc291165b6119fc6cdbc287c36f7f2859e6051dd923bdf47b4c55fd2f8bd0"}, + {url = "https://files.pythonhosted.org/packages/a8/62/9f74f13f3907ca416d8fc7b1c33a8137717a2a2d42364038b9437dcc8040/SQLAlchemy-1.4.41-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0002e829142b2af00b4eaa26c51728f3ea68235f232a2e72a9508a3116bd6ed0"}, + {url = "https://files.pythonhosted.org/packages/b1/1a/e0c11a28c2d2c3c1e74705d4fcb2246434050eed69b70e6acf0ef88adbb0/SQLAlchemy-1.4.41-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:22ff16cedab5b16a0db79f1bc99e46a6ddececb60c396562e50aab58ddb2871c"}, + {url = "https://files.pythonhosted.org/packages/b6/df/51a99ba9b419e15aa39948756f79d6ef2df9ede3288799c1deb43b618799/SQLAlchemy-1.4.41-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5102fb9ee2c258a2218281adcb3e1918b793c51d6c2b4666ce38c35101bb940e"}, + {url = "https://files.pythonhosted.org/packages/bc/a9/f9eb3d4952bfa67f7489732af8db2c31b2e99b6b2f70f786fb6d92b18ebb/SQLAlchemy-1.4.41-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:199a73c31ac8ea59937cc0bf3dfc04392e81afe2ec8a74f26f489d268867846c"}, + {url = "https://files.pythonhosted.org/packages/be/76/912622f9e0b87a9fc58d4d58e9ce459bbd9cd83021c51989afb1839d2162/SQLAlchemy-1.4.41-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0990932f7cca97fece8017414f57fdd80db506a045869d7ddf2dda1d7cf69ecc"}, + {url = "https://files.pythonhosted.org/packages/bf/ed/443a8584b15cbab97f0a5e5ba4974c7b6c989d2ec5a37423946a24619bcf/SQLAlchemy-1.4.41-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8eb8897367a21b578b26f5713833836f886817ee2ffba1177d446fa3f77e67c8"}, + {url = "https://files.pythonhosted.org/packages/bf/f2/69c9f96515b4eb65fac522c8b81ec10666ee4789484b0c123452c1f22505/SQLAlchemy-1.4.41-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ad2b727fc41c7f8757098903f85fafb4bf587ca6605f82d9bf5604bd9c7cded"}, + {url = "https://files.pythonhosted.org/packages/ce/b7/1b65516236b36b55624768f7923c9a8d55ca4ba239b795ea84cb82086718/SQLAlchemy-1.4.41-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2307495d9e0ea00d0c726be97a5b96615035854972cc538f6e7eaed23a35886c"}, + {url = "https://files.pythonhosted.org/packages/d0/ea/86e73fb946694c491a332710d0686f3260b941b3af43502457d3a62512dd/SQLAlchemy-1.4.41-cp310-cp310-win32.whl", hash = "sha256:2082a2d2fca363a3ce21cfa3d068c5a1ce4bf720cf6497fb3a9fc643a8ee4ddd"}, + {url = "https://files.pythonhosted.org/packages/d5/4a/29ce9d2ec5bb2d3e83ad387b956defde6229252259795cd28210a5020740/SQLAlchemy-1.4.41-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5ebeeec5c14533221eb30bad716bc1fd32f509196318fb9caa7002c4a364e4c"}, + {url = "https://files.pythonhosted.org/packages/d6/b7/78d3425a6b3aa486c46259228c1933a22ac4d48b0e6220930973ac852091/SQLAlchemy-1.4.41-cp310-cp310-win_amd64.whl", hash = "sha256:e4b12e3d88a8fffd0b4ca559f6d4957ed91bd4c0613a4e13846ab8729dc5c251"}, + {url = "https://files.pythonhosted.org/packages/de/c2/cb1e60fee76b253b396e31a641e117ba689437b1d9dbecfe8415cb0e8b43/SQLAlchemy-1.4.41-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:13e397a9371ecd25573a7b90bd037db604331cf403f5318038c46ee44908c44d"}, + {url = "https://files.pythonhosted.org/packages/e4/3c/b37bbfe25ebfe129cfa7843e74af3081cca6ae9a893869ba82639479fdf9/SQLAlchemy-1.4.41-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0dcf127bb99458a9d211e6e1f0f3edb96c874dd12f2503d4d8e4f1fd103790b"}, + {url = "https://files.pythonhosted.org/packages/e5/5b/fbaf9a5f3ef900f9eb30644cb74520a7771250a1d0b26a44ca053d3ef4fe/SQLAlchemy-1.4.41-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676d51c9f6f6226ae8f26dc83ec291c088fe7633269757d333978df78d931ab"}, + {url = "https://files.pythonhosted.org/packages/ea/4e/4bcd7e756fa2e989e7eed239bca3c3fc57101b7d0c49864f8e41d202d1ce/SQLAlchemy-1.4.41-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b67fc780cfe2b306180e56daaa411dd3186bf979d50a6a7c2a5b5036575cbdbb"}, + {url = "https://files.pythonhosted.org/packages/f0/97/c6a1bc6e80844c10ee1cb599fa5d8c919fc68b9d9ebed22217cadcfca4c8/SQLAlchemy-1.4.41-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cd767cf5d7252b1c88fcfb58426a32d7bd14a7e4942497e15b68ff5d822b41ad"}, + {url = "https://files.pythonhosted.org/packages/f1/81/638d6bd19baf595959c42c154d83262d609140898eb88866db2f024fcc00/SQLAlchemy-1.4.41-cp39-cp39-win32.whl", hash = "sha256:9c56e19780cd1344fcd362fd6265a15f48aa8d365996a37fab1495cae8fcd97d"}, + {url = "https://files.pythonhosted.org/packages/f4/06/78ab18ec859c7dbdb5182b8463ebb3abac932ad086b9dd15fb60958f9a4f/SQLAlchemy-1.4.41-cp27-cp27m-win_amd64.whl", hash = "sha256:5facb7fd6fa8a7353bbe88b95695e555338fb038ad19ceb29c82d94f62775a05"}, + {url = "https://files.pythonhosted.org/packages/f6/ca/6d666434176ff264e750d14b833a7f2243183a8a69f3a25253f1f0052f09/SQLAlchemy-1.4.41-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ccfd238f766a5bb5ee5545a62dd03f316ac67966a6a658efb63eeff8158a4bbf"}, + {url = "https://files.pythonhosted.org/packages/f8/84/f92a2de0e4a7e82acca2bc74c75295fe5f141ea8ba002e2218cea41d2245/SQLAlchemy-1.4.41-cp36-cp36m-win_amd64.whl", hash = "sha256:eb30cf008850c0a26b72bd1b9be6730830165ce049d239cfdccd906f2685f892"}, + {url = "https://files.pythonhosted.org/packages/fa/5f/150ca2e971231624041de73fbc61b0b16f5139530cbff889213cc00f83f8/SQLAlchemy-1.4.41-cp27-cp27m-win32.whl", hash = "sha256:e570cfc40a29d6ad46c9aeaddbdcee687880940a3a327f2c668dd0e4ef0a441d"}, + {url = "https://files.pythonhosted.org/packages/fe/28/f22792eee334cd83a15ef34b825761ee057d330b9b24d3f1496b95faa557/SQLAlchemy-1.4.41-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:90484a2b00baedad361402c257895b13faa3f01780f18f4a104a2f5c413e4536"}, + {url = "https://files.pythonhosted.org/packages/ff/1c/55bf52c1961ce01164835047ed2c09e44b76d1f18a75841715626f2786b1/SQLAlchemy-1.4.41-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:f37fa70d95658763254941ddd30ecb23fc4ec0c5a788a7c21034fc2305dab7cc"}, +] +"sqlalchemy-utils 0.41.1" = [ + {url = "https://files.pythonhosted.org/packages/73/d8/3863fdfe6b27f6c0dffc650aaa2929f313b33aea615b102279fd46ab550b/SQLAlchemy_Utils-0.41.1-py3-none-any.whl", hash = "sha256:6c96b0768ea3f15c0dc56b363d386138c562752b84f647fb8d31a2223aaab801"}, + {url = "https://files.pythonhosted.org/packages/a3/e0/6906a8a9b8e9deb82923e02e2c1f750c567d69a34f6e1fe566792494a682/SQLAlchemy-Utils-0.41.1.tar.gz", hash = "sha256:a2181bff01eeb84479e38571d2c0718eb52042f9afd8c194d0d02877e84b7d74"}, +] +"sqlalchemy2-stubs 0.0.2a35" = [ + {url = "https://files.pythonhosted.org/packages/bd/a6/289f42af833bf4e6d14e416f79cdeada07d2e5a37fcdd8e469b535fd8fd6/sqlalchemy2_stubs-0.0.2a35-py3-none-any.whl", hash = "sha256:593784ff9fc0dc2ded1895e3322591689db3be06f3ca006e3ef47640baf2d38a"}, + {url = "https://files.pythonhosted.org/packages/c0/70/42d1281f0ea2f5cefea976e6dbd691aea179a26498402d682af180e58b9a/sqlalchemy2-stubs-0.0.2a35.tar.gz", hash = "sha256:bd5d530697d7e8c8504c7fe792ef334538392a5fb7aa7e4f670bfacdd668a19d"}, +] +"sqlmodel 0.0.8" = [ + {url = "https://files.pythonhosted.org/packages/64/ba/ad07004536e94e71f99aaae5e667bb6f7230f7e0fbc0b0266e88960dda5f/sqlmodel-0.0.8.tar.gz", hash = "sha256:3371b4d1ad59d2ffd0c530582c2140b6c06b090b32af9b9c6412986d7b117036"}, + {url = "https://files.pythonhosted.org/packages/90/63/65f95cf5902ccdfccec99de87666b5e039589c19db7ab62b3770171e5685/sqlmodel-0.0.8-py3-none-any.whl", hash = "sha256:0fd805719e0c5d4f22be32eb3ffc856eca3f7f20e8c7aa3e117ad91684b518ee"}, +] +"sqlparse 0.4.4" = [ + {url = "https://files.pythonhosted.org/packages/65/16/10f170ec641ed852611b6c9441b23d10b5702ab5288371feab3d36de2574/sqlparse-0.4.4.tar.gz", hash = "sha256:d446183e84b8349fa3061f0fe7f06ca94ba65b426946ffebe6e3e8295332420c"}, + {url = "https://files.pythonhosted.org/packages/98/5a/66d7c9305baa9f11857f247d4ba761402cea75db6058ff850ed7128957b7/sqlparse-0.4.4-py3-none-any.whl", hash = "sha256:5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3"}, +] +"starlette 0.27.0" = [ + {url = "https://files.pythonhosted.org/packages/06/68/559bed5484e746f1ab2ebbe22312f2c25ec62e4b534916d41a8c21147bf8/starlette-0.27.0.tar.gz", hash = "sha256:6a6b0d042acb8d469a01eba54e9cda6cbd24ac602c4cd016723117d6a7e73b75"}, + {url = "https://files.pythonhosted.org/packages/58/f8/e2cca22387965584a409795913b774235752be4176d276714e15e1a58884/starlette-0.27.0-py3-none-any.whl", hash = "sha256:918416370e846586541235ccd38a474c08b80443ed31c578a418e2209b3eef91"}, +] +"tomli 2.0.1" = [ + {url = "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {url = "https://files.pythonhosted.org/packages/c0/3f/d7af728f075fb08564c5949a9c95e44352e23dee646869fa104a3b2060a3/tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] +"tomlkit 0.11.8" = [ + {url = "https://files.pythonhosted.org/packages/10/37/dd53019ccb72ef7d73fff0bee9e20b16faff9658b47913a35d79e89978af/tomlkit-0.11.8.tar.gz", hash = "sha256:9330fc7faa1db67b541b28e62018c17d20be733177d290a13b24c62d1614e0c3"}, + {url = "https://files.pythonhosted.org/packages/ef/a8/b1c193be753c02e2a94af6e37ee45d3378a74d44fe778c2434a63af92731/tomlkit-0.11.8-py3-none-any.whl", hash = "sha256:8c726c4c202bdb148667835f68d68780b9a003a9ec34167b6c673b38eff2a171"}, +] +"typing-extensions 4.7.1" = [ + {url = "https://files.pythonhosted.org/packages/3c/8b/0111dd7d6c1478bf83baa1cab85c686426c7a6274119aceb2bd9d35395ad/typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, + {url = "https://files.pythonhosted.org/packages/ec/6b/63cc3df74987c36fe26157ee12e09e8f9db4de771e0f3404263117e75b95/typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, +] +"urllib3 1.26.16" = [ + {url = "https://files.pythonhosted.org/packages/c5/05/c214b32d21c0b465506f95c4f28ccbcba15022e000b043b72b3df7728471/urllib3-1.26.16-py2.py3-none-any.whl", hash = "sha256:8d36afa7616d8ab714608411b4a3b13e58f463aee519024578e062e141dce20f"}, + {url = "https://files.pythonhosted.org/packages/e2/7d/539e6f0cf9f0b95b71dd701a56dae89f768cd39fd8ce0096af3546aeb5a3/urllib3-1.26.16.tar.gz", hash = "sha256:8f135f6502756bde6b2a9b28989df5fbe87c9970cecaa69041edcce7f0589b14"}, +] +"uvicorn 0.23.0" = [ + {url = "https://files.pythonhosted.org/packages/41/77/d8962af1c0169cf0883fbd3542c2252f3393f71c527479e5058c66869754/uvicorn-0.23.0.tar.gz", hash = "sha256:d38ab90c0e2c6fe3a054cddeb962cfd5d0e0e6608eaaff4a01d5c36a67f3168c"}, + {url = "https://files.pythonhosted.org/packages/69/53/0230c9d254130fb16481ecb0c037b9ca7a6b25035d397b6f8126e6fbd1ad/uvicorn-0.23.0-py3-none-any.whl", hash = "sha256:479599b2c0bb1b9b394c6d43901a1eb0c1ec72c7d237b5bafea23c5b2d4cdf10"}, +] +"uvloop 0.17.0" = [ + {url = "https://files.pythonhosted.org/packages/04/e3/e8c6b6b2ece6b0ab6033c62344d3de1706ed773d10c1798ee8afb0007b8c/uvloop-0.17.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8efcadc5a0003d3a6e887ccc1fb44dec25594f117a94e3127954c05cf144d811"}, + {url = "https://files.pythonhosted.org/packages/08/f2/99ea33be2a601d74b345605f4843f678b8fc19b6b348c0cf07883791f0b2/uvloop-0.17.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c092a2c1e736086d59ac8e41f9c98f26bbf9b9222a76f21af9dfe949b99b2eb9"}, + {url = "https://files.pythonhosted.org/packages/0e/27/f4f8afa5f34626f5e4fdd6b96734546d293dfe3593a6d73a8785c3e79817/uvloop-0.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cbbe908fda687e39afd6ea2a2f14c2c3e43f2ca88e3a11964b297822358d0e6c"}, + {url = "https://files.pythonhosted.org/packages/13/12/58a06670863b147f2b5bcd35ec16e55c2e811a67e926f62b4c04e6f52755/uvloop-0.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6aafa5a78b9e62493539456f8b646f85abc7093dd997f4976bb105537cf2635e"}, + {url = "https://files.pythonhosted.org/packages/14/58/333a56082bf25dee13cf9e8de5f408d107d75bf6145835ec6d6b2fd35980/uvloop-0.17.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3378eb62c63bf336ae2070599e49089005771cc651c8769aaad72d1bd9385a7c"}, + {url = "https://files.pythonhosted.org/packages/20/9b/920b4b52028a84cc6031b4ce4bef1077d3475e6ce87969a0f0d220807307/uvloop-0.17.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff3d00b70ce95adce264462c930fbaecb29718ba6563db354608f37e49e09024"}, + {url = "https://files.pythonhosted.org/packages/2b/6f/ec3a30f0de00b8d240ab2128d50e4bf20b263065bc51eb0b4bbfaae6c87d/uvloop-0.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c686a47d57ca910a2572fddfe9912819880b8765e2f01dc0dd12a9bf8573e539"}, + {url = "https://files.pythonhosted.org/packages/2c/08/c76bc0325b1a372e6780a169c1da56117591335a08ee19c09e3e6839a195/uvloop-0.17.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7d37dccc7ae63e61f7b96ee2e19c40f153ba6ce730d8ba4d3b4e9738c1dccc1b"}, + {url = "https://files.pythonhosted.org/packages/2c/70/c4162951c8c3a4a8b19a62b2668517e16b4e74499e040c07c7d99dad5126/uvloop-0.17.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8887d675a64cfc59f4ecd34382e5b4f0ef4ae1da37ed665adba0c2badf0d6578"}, + {url = "https://files.pythonhosted.org/packages/33/f5/94d267b8286fd9390a3276843300461edaa65431b428634056994b24b16a/uvloop-0.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0949caf774b9fcefc7c5756bacbbbd3fc4c05a6b7eebc7c7ad6f825b23998d6d"}, + {url = "https://files.pythonhosted.org/packages/5b/68/08d63f6e426fdb18d718251de786e784254985f633bbd16685e0befb5b04/uvloop-0.17.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3db8de10ed684995a7f34a001f15b374c230f7655ae840964d51496e2f8a8474"}, + {url = "https://files.pythonhosted.org/packages/5d/bc/c1ef0b1c8faa3960b22f5809ebfd1eaa009e441b28b697f8871c31fc51d7/uvloop-0.17.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6708f30db9117f115eadc4f125c2a10c1a50d711461699a0cbfaa45b9a78e376"}, + {url = "https://files.pythonhosted.org/packages/7f/17/e300f183e5cbcc197eaa62c0c020072b778039297b0df896b6274a73a7da/uvloop-0.17.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45cea33b208971e87a31c17622e4b440cac231766ec11e5d22c76fab3bf9df62"}, + {url = "https://files.pythonhosted.org/packages/83/c0/9ade5760e31bc67fc30e74cf896cc72f7f8f8121b0ac64113c684571a22b/uvloop-0.17.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a5abddb3558d3f0a78949c750644a67be31e47936042d4f6c888dd6f3c95f4aa"}, + {url = "https://files.pythonhosted.org/packages/88/0b/f795eeada85d2971b0718a45683e673ad2211ba8d68b166d1f917fc0b86f/uvloop-0.17.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:9b09e0f0ac29eee0451d71798878eae5a4e6a91aa275e114037b27f7db72702d"}, + {url = "https://files.pythonhosted.org/packages/8a/ff/bb80345a3fc39b0ce1ad27e8906874337a29dfb77e6d1e26740439be4a93/uvloop-0.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ebeeec6a6641d0adb2ea71dcfb76017602ee2bfd8213e3fcc18d8f699c5104f"}, + {url = "https://files.pythonhosted.org/packages/8f/93/6e0ce46158943650c6f15c4acfb008d9314fe670a1376399cdea295bf71e/uvloop-0.17.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a4aee22ece20958888eedbad20e4dbb03c37533e010fb824161b4f05e641f738"}, + {url = "https://files.pythonhosted.org/packages/90/75/e856169afc8c4676402a2c45ecb409f25e3dca4e17a5291bf6804006deba/uvloop-0.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:68532f4349fd3900b839f588972b3392ee56042e440dd5873dfbbcd2cc67617c"}, + {url = "https://files.pythonhosted.org/packages/93/f8/5ba5eb1e005e2419d455d8d677211bf58ba500f204236e0b089c1a6067be/uvloop-0.17.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:23609ca361a7fc587031429fa25ad2ed7242941adec948f9d10c045bfecab06b"}, + {url = "https://files.pythonhosted.org/packages/97/ae/e60b67eca95e9bf8f3407996acc478a8df2a0cda4cce5c3d231a831d79ba/uvloop-0.17.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:30babd84706115626ea78ea5dbc7dd8d0d01a2e9f9b306d24ca4ed5796c66ded"}, + {url = "https://files.pythonhosted.org/packages/a9/17/e0a10e6b5a1ace1861ba496981fed35dd806c81fa18260e6e631f2713c3c/uvloop-0.17.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2a6149e1defac0faf505406259561bc14b034cdf1d4711a3ddcdfbaa8d825a05"}, + {url = "https://files.pythonhosted.org/packages/ab/03/ed3a0d08c9d307e8babdbed7fc6c54b273602adb3fa41748b6c1785108b3/uvloop-0.17.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1e507c9ee39c61bfddd79714e4f85900656db1aec4d40c6de55648e85c2799c"}, + {url = "https://files.pythonhosted.org/packages/ad/14/f791682bc94a80b03431de5d753484ac1c8a5cc3b966fd21f053ad14d5c8/uvloop-0.17.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ce9f61938d7155f79d3cb2ffa663147d4a76d16e08f65e2c66b77bd41b356718"}, + {url = "https://files.pythonhosted.org/packages/b1/0c/f08c6863c9e0a6823b69fbbc6753a3e4f47c3a48628ce6e8370bd39b76e7/uvloop-0.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:864e1197139d651a76c81757db5eb199db8866e13acb0dfe96e6fc5d1cf45fc4"}, + {url = "https://files.pythonhosted.org/packages/ba/86/6dda1760481abf244cbd3908b79a4520d757040ca9ec37a79fc0fd01e2a0/uvloop-0.17.0.tar.gz", hash = "sha256:0ddf6baf9cf11a1a22c71487f39f15b2cf78eb5bde7e5b45fbb99e8a9d91b9e1"}, + {url = "https://files.pythonhosted.org/packages/c5/56/745a5e615edbec0e6062397782285fbb01c50bf659e2b22489bdd9f9318f/uvloop-0.17.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:307958f9fc5c8bb01fad752d1345168c0abc5d62c1b72a4a8c6c06f042b45b20"}, + {url = "https://files.pythonhosted.org/packages/c6/b3/60fc0f21b58b86335e2435b2cd6a9d75cb79d99787f15663fae01406c8c5/uvloop-0.17.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2deae0b0fb00a6af41fe60a675cec079615b01d68beb4cc7b722424406b126a8"}, + {url = "https://files.pythonhosted.org/packages/d3/85/2fea43f570b32027dbf11426ea88aea9e4525f40f6e0b7017a74ab7d57ad/uvloop-0.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d97672dc709fa4447ab83276f344a165075fd9f366a97b712bdd3fee05efae8"}, + {url = "https://files.pythonhosted.org/packages/fa/28/8a3c2f067014018ba6647c39af64e3b45e5391cf85ba882fa824bda9dba3/uvloop-0.17.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dbbaf9da2ee98ee2531e0c780455f2841e4675ff580ecf93fe5c48fe733b5667"}, + {url = "https://files.pythonhosted.org/packages/fb/11/fef3cf9f2aa23a7daf84c39dbd66dcd562479ffc2c064496d0525adc4b43/uvloop-0.17.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1436c8673c1563422213ac6907789ecb2b070f5939b9cbff9ef7113f2b531595"}, +] +"virtualenv 20.24.0" = [ + {url = "https://files.pythonhosted.org/packages/26/c6/169a384bfe44de610f5bbc03b3d4cc23a5a64a75be11864a033f2fe195db/virtualenv-20.24.0.tar.gz", hash = "sha256:e2a7cef9da880d693b933db7654367754f14e20650dc60e8ee7385571f8593a3"}, + {url = "https://files.pythonhosted.org/packages/c5/d5/f914b715f8b4c2ae8ca10112d389c04bed368ddd8888b70dafe740269bb5/virtualenv-20.24.0-py3-none-any.whl", hash = "sha256:18d1b37fc75cc2670625702d76849a91ebd383768b4e91382a8d51be3246049e"}, +] +"watchfiles 0.19.0" = [ + {url = "https://files.pythonhosted.org/packages/09/82/a9e1b9741cefa592dcd85f8ebdf739a24c6572b5ab58ce65589f340b3cff/watchfiles-0.19.0-cp37-abi3-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:18b28f6ad871b82df9542ff958d0c86bb0d8310bb09eb8e87d97318a3b5273af"}, + {url = "https://files.pythonhosted.org/packages/1e/68/86742189038396f0b8df17556690c5fdb350e74c2b947782a165c6f69acb/watchfiles-0.19.0-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:cae3dde0b4b2078f31527acff6f486e23abed307ba4d3932466ba7cdd5ecec79"}, + {url = "https://files.pythonhosted.org/packages/29/8a/6293b04bcb667e43808921cc8794f6231aa4ea819d0cdb659b3e3cf862f1/watchfiles-0.19.0-cp37-abi3-win_arm64.whl", hash = "sha256:0089c6dc24d436b373c3c57657bf4f9a453b13767150d17284fc6162b2791911"}, + {url = "https://files.pythonhosted.org/packages/2f/7f/0fb6699188e4a553e10e94a479fe4280a17d29f3a8b8b8f11d6291a82006/watchfiles-0.19.0-cp37-abi3-macosx_10_7_x86_64.whl", hash = "sha256:91633e64712df3051ca454ca7d1b976baf842d7a3640b87622b323c55f3345e7"}, + {url = "https://files.pythonhosted.org/packages/47/6a/de51bae85bcbf17dc9389589162c8996ed72bf6e97b81c8c6d60666e4768/watchfiles-0.19.0-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:79c533ff593db861ae23436541f481ec896ee3da4e5db8962429b441bbaae16e"}, + {url = "https://files.pythonhosted.org/packages/50/0e/026461f88bf5dafd9bfee6fcf7fe2deddece9d3e0c86d4b7986b0e7c5df3/watchfiles-0.19.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9afd0d69429172c796164fd7fe8e821ade9be983f51c659a38da3faaaaac44dc"}, + {url = "https://files.pythonhosted.org/packages/64/22/88e5143e4397bf30cde669b78fb2a2fcdbaaa0e8b5fea5ae402d2e5a7bc2/watchfiles-0.19.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f3920b1285a7d3ce898e303d84791b7bf40d57b7695ad549dc04e6a44c9f120"}, + {url = "https://files.pythonhosted.org/packages/66/1c/5b14d4be9f02a96c2f06b92d9bc3335ced1f4e1c7ea6e522fa4c9a517f28/watchfiles-0.19.0-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:3d7d267d27aceeeaa3de0dd161a0d64f0a282264d592e335fff7958cc0cbae7c"}, + {url = "https://files.pythonhosted.org/packages/79/65/08d06f63c2d983c733e225b9cd2ba9f36b7ccb1095854b66d0010ca36013/watchfiles-0.19.0-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c0376deac92377817e4fb8f347bf559b7d44ff556d9bc6f6208dd3f79f104aaf"}, + {url = "https://files.pythonhosted.org/packages/8a/1c/6869ad3639b23b3a88ba5a61aacffaf425666e416cd4e0d8b6b47e666a68/watchfiles-0.19.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b538014a87f94d92f98f34d3e6d2635478e6be6423a9ea53e4dd96210065e193"}, + {url = "https://files.pythonhosted.org/packages/8e/e1/85f1454e0c6335e32d1bd900237d34f76db2b261a515bc47188d8e3eea65/watchfiles-0.19.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:b6577b8c6c8701ba8642ea9335a129836347894b666dd1ec2226830e263909d3"}, + {url = "https://files.pythonhosted.org/packages/92/e5/a77ee68cbeedf342f317214a5b48f4c191d021b57836ef3b2c39bcbb516c/watchfiles-0.19.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fac19dc9cbc34052394dbe81e149411a62e71999c0a19e1e09ce537867f95ae0"}, + {url = "https://files.pythonhosted.org/packages/9b/51/104178107563657a7fe5c5d3a0426fa5f61f837698bc349bc93761ce07a5/watchfiles-0.19.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68dce92b29575dda0f8d30c11742a8e2b9b8ec768ae414b54f7453f27bdf9545"}, + {url = "https://files.pythonhosted.org/packages/9d/d1/708fc7475e33990792262f23d054692da98b4b8c847772ca12d868d55ac5/watchfiles-0.19.0-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:5569fc7f967429d4bc87e355cdfdcee6aabe4b620801e2cf5805ea245c06097c"}, + {url = "https://files.pythonhosted.org/packages/aa/3b/c648597eb3611c4b72eda09aa9069773f6b4459bd7142cf200dcd34acfcf/watchfiles-0.19.0-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c75eff897786ee262c9f17a48886f4e98e6cfd335e011c591c305e5d083c056"}, + {url = "https://files.pythonhosted.org/packages/ad/5d/bc89d194bac1baafa4e98bade4d778d5b2918e4b9cc45284ec78c90b135c/watchfiles-0.19.0-cp37-abi3-win32.whl", hash = "sha256:176a9a7641ec2c97b24455135d58012a5be5c6217fc4d5fef0b2b9f75dbf5154"}, + {url = "https://files.pythonhosted.org/packages/b0/6a/a89df14975f21b41d199f745d016dd6cebce232cb7634d3cd441ca67a1eb/watchfiles-0.19.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5471582658ea56fca122c0f0d0116a36807c63fefd6fdc92c71ca9a4491b6b48"}, + {url = "https://files.pythonhosted.org/packages/b1/95/5fa75076399195838c618e06418dbc4996613e3e57fbecd0c1d86764afbf/watchfiles-0.19.0-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:09ea3397aecbc81c19ed7f025e051a7387feefdb789cf768ff994c1228182fda"}, + {url = "https://files.pythonhosted.org/packages/b2/da/be8664576654aa13312d5b9e854b9c6b7f759125119e5b69e6cdc74deffd/watchfiles-0.19.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20b44221764955b1e703f012c74015306fb7e79a00c15370785f309b1ed9aa8d"}, + {url = "https://files.pythonhosted.org/packages/b3/17/d9453f774dd079fbe7d51565d58006f5059fc17c2fbcf952ef176fbb8657/watchfiles-0.19.0.tar.gz", hash = "sha256:d9b073073e048081e502b6c6b0b88714c026a1a4c890569238d04aca5f9ca74b"}, + {url = "https://files.pythonhosted.org/packages/f2/40/cd125248cd1bb1fd449d2439d9a1b599c9f6dc255645838ce0be96b28337/watchfiles-0.19.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb5d45c4143c1dd60f98a16187fd123eda7248f84ef22244818c18d531a249d1"}, + {url = "https://files.pythonhosted.org/packages/fe/1d/3948e864ae4f4c87cd32d980aff9b7b8767f74e0d8ea0b70a07a86226087/watchfiles-0.19.0-cp37-abi3-win_amd64.whl", hash = "sha256:945be0baa3e2440151eb3718fd8846751e8b51d8de7b884c90b17d271d34cae8"}, +] +"websockets 11.0.3" = [ + {url = "https://files.pythonhosted.org/packages/03/28/3a51ffcf51ac45746639f83128908bbb1cd212aa631e42d15a7acebce5cb/websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e052b8467dd07d4943936009f46ae5ce7b908ddcac3fda581656b1b19c083d9b"}, + {url = "https://files.pythonhosted.org/packages/0a/84/68b848a373493b58615d6c10e9e8ccbaadfd540f84905421739a807704f8/websockets-11.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa5003845cdd21ac0dc6c9bf661c5beddd01116f6eb9eb3c8e272353d45b3288"}, + {url = "https://files.pythonhosted.org/packages/0f/d8/a997d3546aef9cc995a1126f7d7ade96c0e16c1a0efb9d2d430aee57c925/websockets-11.0.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:619d9f06372b3a42bc29d0cd0354c9bb9fb39c2cbc1a9c5025b4538738dbffaf"}, + {url = "https://files.pythonhosted.org/packages/14/fc/5cbbf439c925e1e184a0392ec477a30cee2fabc0e63807c1d4b6d570fb52/websockets-11.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b30c6590146e53149f04e85a6e4fcae068df4289e31e4aee1fdf56a0dead8f97"}, + {url = "https://files.pythonhosted.org/packages/16/49/ae616bd221efba84a3d78737b417f704af1ffa36f40dcaba5eb954dd4753/websockets-11.0.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e848f46a58b9fcf3d06061d17be388caf70ea5b8cc3466251963c8345e13f7eb"}, + {url = "https://files.pythonhosted.org/packages/19/d3/2ea3f95d83033675144b0848a0ae2e4998b3f763da09ec3df6bce97ea4e6/websockets-11.0.3-cp37-cp37m-win32.whl", hash = "sha256:e590228200fcfc7e9109509e4d9125eace2042fd52b595dd22bbc34bb282307f"}, + {url = "https://files.pythonhosted.org/packages/1b/3d/3dc77699fa4d003f2e810c321592f80f62b81d7b78483509de72ffe581fd/websockets-11.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0ee68fe502f9031f19d495dae2c268830df2760c0524cbac5d759921ba8c8e82"}, + {url = "https://files.pythonhosted.org/packages/20/62/5c6039c4069912adb27889ddd000403a2de9e0fe6aebe439b4e6b128a6b8/websockets-11.0.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e6316827e3e79b7b8e7d8e3b08f4e331af91a48e794d5d8b099928b6f0b85f20"}, + {url = "https://files.pythonhosted.org/packages/25/25/48540419005d07ed2d368a7eafb44ed4f33a2691ae4c210850bf31123c4a/websockets-11.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:03aae4edc0b1c68498f41a6772d80ac7c1e33c06c6ffa2ac1c27a07653e79d6f"}, + {url = "https://files.pythonhosted.org/packages/27/e9/605b0618d0864e9be7c2a78f22bff57aba9cf56b9fccde3205db9023ae22/websockets-11.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:92b2065d642bf8c0a82d59e59053dd2fdde64d4ed44efe4870fa816c1232647b"}, + {url = "https://files.pythonhosted.org/packages/30/a5/d641f2a9a4b4079cfddbb0726fc1b914be76a610aaedb45e4760899a4ce1/websockets-11.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bee9fcb41db2a23bed96c6b6ead6489702c12334ea20a297aa095ce6d31370d0"}, + {url = "https://files.pythonhosted.org/packages/32/2c/ab8ea64e9a7d8bf62a7ea7a037fb8d328d8bd46dbfe083787a9d452a148e/websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b67c6f5e5a401fc56394f191f00f9b3811fe843ee93f4a70df3c389d1adf857d"}, + {url = "https://files.pythonhosted.org/packages/36/19/0da435afb26a6c47c0c045a82e414912aa2ac10de5721276a342bd9fdfee/websockets-11.0.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:332d126167ddddec94597c2365537baf9ff62dfcc9db4266f263d455f2f031cb"}, + {url = "https://files.pythonhosted.org/packages/38/30/01a10fbf4cc1e7ffa07be9b0401501918fc9433d71fb7da4cfcef3bd26ca/websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8531fdcad636d82c517b26a448dcfe62f720e1922b33c81ce695d0edb91eb931"}, + {url = "https://files.pythonhosted.org/packages/38/ed/b8b133416536b6816e480594864e5950051db522714623eefc9e5275ec04/websockets-11.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6505c1b31274723ccaf5f515c1824a4ad2f0d191cec942666b3d0f3aa4cb4007"}, + {url = "https://files.pythonhosted.org/packages/44/a8/66c3a66b70b01a6c55fde486298766177fa11dd0d3a2c1cfc6820f25b4dc/websockets-11.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2529338a6ff0eb0b50c7be33dc3d0e456381157a31eefc561771ee431134a97f"}, + {url = "https://files.pythonhosted.org/packages/47/96/9d5749106ff57629b54360664ae7eb9afd8302fad1680ead385383e33746/websockets-11.0.3-py3-none-any.whl", hash = "sha256:6681ba9e7f8f3b19440921e99efbb40fc89f26cd71bf539e45d8c8a25c976dc6"}, + {url = "https://files.pythonhosted.org/packages/58/05/2efb520317340ece74bfc4d88e8f011dd71a4e6c263000bfffb71a343685/websockets-11.0.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1a99a7a71631f0efe727c10edfba09ea6bee4166a6f9c19aafb6c0b5917d09c"}, + {url = "https://files.pythonhosted.org/packages/58/0a/7570e15661a0a546c3a1152d95fe8c05480459bab36247f0acbf41f01a41/websockets-11.0.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bceab846bac555aff6427d060f2fcfff71042dba6f5fca7dc4f75cac815e57ca"}, + {url = "https://files.pythonhosted.org/packages/58/68/9403771de1b1c21a2e878e4841815af8c9f8893b094654934e2a5ee4dbc8/websockets-11.0.3-cp38-cp38-win32.whl", hash = "sha256:f61bdb1df43dc9c131791fbc2355535f9024b9a04398d3bd0684fc16ab07df74"}, + {url = "https://files.pythonhosted.org/packages/66/89/799f595c67b97a8a17e13d2764e088f631616bd95668aaa4c04b7cada136/websockets-11.0.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df41b9bc27c2c25b486bae7cf42fccdc52ff181c8c387bfd026624a491c2671b"}, + {url = "https://files.pythonhosted.org/packages/6b/ca/65d6986665888494eca4d5435a9741c822022996f0f4200c57ce4b9242f7/websockets-11.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:660e2d9068d2bedc0912af508f30bbeb505bbbf9774d98def45f68278cea20d3"}, + {url = "https://files.pythonhosted.org/packages/70/fc/71377f36ef3049f3bc7db7c0f3a7696929d5f836d7a18777131d994192a9/websockets-11.0.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34fd59a4ac42dff6d4681d8843217137f6bc85ed29722f2f7222bd619d15e95b"}, + {url = "https://files.pythonhosted.org/packages/72/89/0d150939f2e592ed78c071d69237ac1c872462cc62a750c5f592f3d4ab18/websockets-11.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:97b52894d948d2f6ea480171a27122d77af14ced35f62e5c892ca2fae9344311"}, + {url = "https://files.pythonhosted.org/packages/78/b2/df5452031b02b857851139806308f2af7c749069e25bfe15f2d559ade6e7/websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ac56b661e60edd453585f4bd68eb6a29ae25b5184fd5ba51e97652580458998"}, + {url = "https://files.pythonhosted.org/packages/82/3c/00f051abcf88aec5e952a8840076749b0b26a30c219dcae8ba70200998aa/websockets-11.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:86d2a77fd490ae3ff6fae1c6ceaecad063d3cc2320b44377efdde79880e11526"}, + {url = "https://files.pythonhosted.org/packages/89/8f/707a05d5725f956c78d252a5fd73b89fa3ac57dd3959381c2d1acb41cb13/websockets-11.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:01f5567d9cf6f502d655151645d4e8b72b453413d3819d2b6f1185abc23e82dd"}, + {url = "https://files.pythonhosted.org/packages/8a/77/a04d2911f6e2b9e781ce7ffc1e8516b54b85f985369eec8c853fd619d8e8/websockets-11.0.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:69269f3a0b472e91125b503d3c0b3566bda26da0a3261c49f0027eb6075086d1"}, + {url = "https://files.pythonhosted.org/packages/8a/bd/a5e5973899d78d44a540f50a9e30b01c6771e8bf7883204ee762060cf95a/websockets-11.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4841ed00f1026dfbced6fca7d963c4e7043aa832648671b5138008dc5a8f6d99"}, + {url = "https://files.pythonhosted.org/packages/8b/97/34178f5f7c29e679372d597cebfeff2aa45991d741d938117d4616e81a74/websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d5023a4b6a5b183dc838808087033ec5df77580485fc533e7dab2567851b0a4"}, + {url = "https://files.pythonhosted.org/packages/8c/a8/e81533499f84ef6cdd95d11d5b05fa827c0f097925afd86f16e6a2631d8e/websockets-11.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b58cbf0697721120866820b89f93659abc31c1e876bf20d0b3d03cef14faf84d"}, + {url = "https://files.pythonhosted.org/packages/8f/7b/4d4ecd29be7d08486e38f987a6603c491296d1e33fe55127d79aebb0333e/websockets-11.0.3-cp310-cp310-win32.whl", hash = "sha256:2d903ad4419f5b472de90cd2d40384573b25da71e33519a67797de17ef849b69"}, + {url = "https://files.pythonhosted.org/packages/8f/f2/8a3eb016be19743c7eb9e67c855df0fdfa5912534ffaf83a05b62667d761/websockets-11.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8c82f11964f010053e13daafdc7154ce7385ecc538989a354ccc7067fd7028fd"}, + {url = "https://files.pythonhosted.org/packages/94/8c/266155c14b7a26deca6fa4c4d5fd15b0ab32725d78a2acfcf6b24943585d/websockets-11.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:b16fff62b45eccb9c7abb18e60e7e446998093cdcb50fed33134b9b6878836de"}, + {url = "https://files.pythonhosted.org/packages/98/a7/0ed69892981351e5acf88fac0ff4c801fabca2c3bdef9fca4c7d3fde8c53/websockets-11.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:1d2256283fa4b7f4c7d7d3e84dc2ece74d341bce57d5b9bf385df109c2a1a82f"}, + {url = "https://files.pythonhosted.org/packages/99/23/43071c989c0f87f612e7bccee98d00b04bddd3aca0cdc1ffaf31f6f8a4b4/websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c114e8da9b475739dde229fd3bc6b05a6537a88a578358bc8eb29b4030fac9c9"}, + {url = "https://files.pythonhosted.org/packages/9a/6e/0fd7274042f46acb589161407f4b505b44c68d369437ce919bae1fa9b8c4/websockets-11.0.3-cp39-cp39-win32.whl", hash = "sha256:c7f3cb904cce8e1be667c7e6fef4516b98d1a6a0635a58a57528d577ac18a128"}, + {url = "https://files.pythonhosted.org/packages/a0/1a/3da73e69ebc00649d11ed836541c92c1a2df0b8a8aa641a2c8746e7c2b9c/websockets-11.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3580dd9c1ad0701169e4d6fc41e878ffe05e6bdcaf3c412f9d559389d0c9e016"}, + {url = "https://files.pythonhosted.org/packages/a0/39/acc3d4b15c5207ef7cca823c37eca8c74e3e1a1a63a397798986be3bdef7/websockets-11.0.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68b977f21ce443d6d378dbd5ca38621755f2063d6fdb3335bda981d552cfff86"}, + {url = "https://files.pythonhosted.org/packages/a6/1b/5c83c40f8d3efaf0bb2fdf05af94fb920f74842b7aaf31d7598e3ee44d58/websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcacf2c7a6c3a84e720d1bb2b543c675bf6c40e460300b628bab1b1efc7c034c"}, + {url = "https://files.pythonhosted.org/packages/a6/9c/2356ecb952fd3992b73f7a897d65e57d784a69b94bb8d8fd5f97531e5c02/websockets-11.0.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:279e5de4671e79a9ac877427f4ac4ce93751b8823f276b681d04b2156713b9dd"}, + {url = "https://files.pythonhosted.org/packages/a7/8c/7100e9cf310fe1d83d1ae1322203f4eb2b767a7c2b301c1e70db6270306f/websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de36fe9c02995c7e6ae6efe2e205816f5f00c22fd1fbf343d4d18c3d5ceac2f5"}, + {url = "https://files.pythonhosted.org/packages/a7/f7/1e852351e8073c32885172a6bef64c95d14c13ff3634b01d4a1086321491/websockets-11.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9d9acd80072abcc98bd2c86c3c9cd4ac2347b5a5a0cae7ed5c0ee5675f86d9af"}, + {url = "https://files.pythonhosted.org/packages/a9/5e/b25c60067d700e811dccb4e3c318eeadd3a19d8b3620de9f97434af777a7/websockets-11.0.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:def07915168ac8f7853812cc593c71185a16216e9e4fa886358a17ed0fd9fcf6"}, + {url = "https://files.pythonhosted.org/packages/b2/ec/56bdd12d847e4fc2d0a7ba2d7f1476f79cda50599d11ffb6080b86f21ef1/websockets-11.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffd7dcaf744f25f82190856bc26ed81721508fc5cbf2a330751e135ff1283564"}, + {url = "https://files.pythonhosted.org/packages/b5/94/ac47552208583d5dbcce468430c1eb2ae18962f6b3a694a2b7727cc60d4a/websockets-11.0.3-cp311-cp311-win32.whl", hash = "sha256:e1459677e5d12be8bbc7584c35b992eea142911a6236a3278b9b5ce3326f282c"}, + {url = "https://files.pythonhosted.org/packages/b5/a8/8900184ab0b06b6e620ba7e92cf2faa5caa9ba86e148541b8fff1c7b6646/websockets-11.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9f59a3c656fef341a99e3d63189852be7084c0e54b75734cde571182c087b152"}, + {url = "https://files.pythonhosted.org/packages/b6/96/0d586c25d043aeab9457dad8e407251e3baf314d871215f91847e7b995c4/websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e063b1865974611313a3849d43f2c3f5368093691349cf3c7c8f8f75ad7cb280"}, + {url = "https://files.pythonhosted.org/packages/b9/6b/26b28115b46e23e74ede76d95792eedfe8c58b21f4daabfff1e9f159c8fe/websockets-11.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d67ac60a307f760c6e65dad586f556dde58e683fab03323221a4e530ead6f74d"}, + {url = "https://files.pythonhosted.org/packages/ba/6c/5c0322b2875e8395e6bf0eff11f43f3e25da7ef5b12f4d908cd3a19ea841/websockets-11.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:54c6e5b3d3a8936a4ab6870d46bdd6ec500ad62bde9e44462c32d18f1e9a8e54"}, + {url = "https://files.pythonhosted.org/packages/c0/21/cb9dfbbea8dc0ad89ced52630e7e61edb425fb9fdc6002f8d0c5dd26b94b/websockets-11.0.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:777354ee16f02f643a4c7f2b3eff8027a33c9861edc691a2003531f5da4f6bc8"}, + {url = "https://files.pythonhosted.org/packages/c0/a8/a8a582ebeeecc8b5f332997d44c57e241748f8a9856e06a38a5a13b30796/websockets-11.0.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1f0524f203e3bd35149f12157438f406eff2e4fb30f71221c8a5eceb3617b6b"}, + {url = "https://files.pythonhosted.org/packages/c4/5b/60eccd7e9703bbe93fc4167d1e7ada7e8e8e51544122198d63fd8e3460b7/websockets-11.0.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fb06eea71a00a7af0ae6aefbb932fb8a7df3cb390cc217d51a9ad7343de1b8d0"}, + {url = "https://files.pythonhosted.org/packages/c4/f5/15998b164c183af0513bba744b51ecb08d396ff86c0db3b55d62624d1f15/websockets-11.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1fdf26fa8a6a592f8f9235285b8affa72748dc12e964a5518c6c5e8f916716f7"}, + {url = "https://files.pythonhosted.org/packages/c6/91/f36454b87edf10a95be9c7212d2dcb8c606ddbf7a183afdc498933acdd19/websockets-11.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1553cb82942b2a74dd9b15a018dce645d4e68674de2ca31ff13ebc2d9f283788"}, + {url = "https://files.pythonhosted.org/packages/c9/fb/ae5ed4be3514287cf8f6c348c87e1392a6e3f4d6eadae75c18847a2f84b6/websockets-11.0.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7622a89d696fc87af8e8d280d9b421db5133ef5b29d3f7a1ce9f1a7bf7fcfa11"}, + {url = "https://files.pythonhosted.org/packages/ca/20/25211be61d50189650fb0ec6084b6d6339f5c7c6436a6c217608dcb553e4/websockets-11.0.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4b253869ea05a5a073ebfdcb5cb3b0266a57c3764cf6fe114e4cd90f4bfa5f5e"}, + {url = "https://files.pythonhosted.org/packages/d1/ec/7e2b9bebc2e9b4a48404144106bbc6a7ace781feeb0e6a3829551e725fa5/websockets-11.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8a34e13a62a59c871064dfd8ffb150867e54291e46d4a7cf11d02c94a5275bae"}, + {url = "https://files.pythonhosted.org/packages/d8/3b/2ed38e52eed4cf277f9df5f0463a99199a04d9e29c9e227cfafa57bd3993/websockets-11.0.3.tar.gz", hash = "sha256:88fc51d9a26b10fc331be344f1781224a375b78488fc343620184e95a4b27016"}, + {url = "https://files.pythonhosted.org/packages/d9/36/5741e62ccf629c8e38cc20f930491f8a33ce7dba972cae93dba3d6f02552/websockets-11.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f1a3f10f836fab6ca6efa97bb952300b20ae56b409414ca85bff2ad241d2a61"}, + {url = "https://files.pythonhosted.org/packages/de/0e/d7274e4d41d7b34f204744c27a23707be2ecefaf6f7df2145655f086ecd7/websockets-11.0.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:41f696ba95cd92dc047e46b41b26dd24518384749ed0d99bea0a941ca87404c4"}, + {url = "https://files.pythonhosted.org/packages/e1/76/88640f8aeac7eb0d058b913e7bb72682f8d569db44c7d30e576ec4777ce1/websockets-11.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3ccc8a0c387629aec40f2fc9fdcb4b9d5431954f934da3eaf16cdc94f67dbfac"}, + {url = "https://files.pythonhosted.org/packages/e1/7c/0ad6e7ef0a054d73092f616d20d3d9bd3e1b837554cb20a52d8dd9f5b049/websockets-11.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:e7837cb169eca3b3ae94cc5787c4fed99eef74c0ab9506756eea335e0d6f3ed8"}, + {url = "https://files.pythonhosted.org/packages/e2/2f/3ad8ac4a9dc9d685e098e534180a36ed68fe2e85e82e225e00daec86bb94/websockets-11.0.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f2e58f2c36cc52d41f2659e4c0cbf7353e28c8c9e63e30d8c6d3494dc9fdedcf"}, + {url = "https://files.pythonhosted.org/packages/e9/26/1dfaa81788f61c485b4d65f1b28a19615e39f9c45100dce5e2cbf5ad1352/websockets-11.0.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f467ba0050b7de85016b43f5a22b46383ef004c4f672148a8abf32bc999a87f0"}, + {url = "https://files.pythonhosted.org/packages/eb/fb/2af7fc3ce2c3f1378d48a15802b4ff2caf6c0dfac13291e73c557caf04f7/websockets-11.0.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:42cc5452a54a8e46a032521d7365da775823e21bfba2895fb7b77633cce031bb"}, + {url = "https://files.pythonhosted.org/packages/ec/3f/0c5cae14e9e86401105833383405787ae4caddd476a8fc5561259253dab7/websockets-11.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a073fc9ab1c8aff37c99f11f1641e16da517770e31a37265d2755282a5d28aa"}, + {url = "https://files.pythonhosted.org/packages/ed/45/466944e00b324ae3a1fddb305b4abf641f582e131548f07bcd970971b154/websockets-11.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ed058398f55163a79bb9f06a90ef9ccc063b204bb346c4de78efc5d15abfe602"}, + {url = "https://files.pythonhosted.org/packages/f3/82/2d1f3395d47fab65fa8b801e2251b324300ed8db54753b6fb7919cef0c11/websockets-11.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84d27a4832cc1a0ee07cdcf2b0629a8a72db73f4cf6de6f0904f6661227f256f"}, + {url = "https://files.pythonhosted.org/packages/f4/3f/65dfa50084a06ab0a05f3ca74195c2c17a1c075b8361327d831ccce0a483/websockets-11.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c792ea4eabc0159535608fc5658a74d1a81020eb35195dd63214dcf07556f67e"}, +] +"wheel 0.40.0" = [ + {url = "https://files.pythonhosted.org/packages/61/86/cc8d1ff2ca31a312a25a708c891cf9facbad4eae493b3872638db6785eb5/wheel-0.40.0-py3-none-any.whl", hash = "sha256:d236b20e7cb522daf2390fa84c55eea81c5c30190f90f29ae2ca1ad8355bf247"}, + {url = "https://files.pythonhosted.org/packages/fc/ef/0335f7217dd1e8096a9e8383e1d472aa14717878ffe07c4772e68b6e8735/wheel-0.40.0.tar.gz", hash = "sha256:cd1196f3faee2b31968d626e1731c94f99cbdb67cf5a46e4f5656cbee7738873"}, +] +"wrapt 1.15.0" = [ + {url = "https://files.pythonhosted.org/packages/0c/6e/f80c23efc625c10460240e31dcb18dd2b34b8df417bc98521fbfd5bc2e9a/wrapt-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923"}, + {url = "https://files.pythonhosted.org/packages/0f/9a/179018bb3f20071f39597cd38fc65d6285d7b89d57f6c51f502048ed28d9/wrapt-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d"}, + {url = "https://files.pythonhosted.org/packages/12/5a/fae60a8bc9b07a3a156989b79e14c58af05ab18375749ee7c12b2f0dddbd/wrapt-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1"}, + {url = "https://files.pythonhosted.org/packages/18/f6/659d7c431a57da9c9a86945834ab2bf512f1d9ebefacea49135a0135ef1a/wrapt-1.15.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034"}, + {url = "https://files.pythonhosted.org/packages/1e/3c/cb96dbcafbf3a27413fb15e0a1997c4610283f895dc01aca955cd2fda8b9/wrapt-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c"}, + {url = "https://files.pythonhosted.org/packages/20/01/baec2650208284603961d61f53ee6ae8e3eff63489c7230dff899376a6f6/wrapt-1.15.0-cp35-cp35m-win_amd64.whl", hash = "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"}, + {url = "https://files.pythonhosted.org/packages/21/42/36c98e9c024978f52c218f22eba1addd199a356ab16548af143d3a72ac0d/wrapt-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09"}, + {url = "https://files.pythonhosted.org/packages/23/0a/9964d7141b8c5e31c32425d3412662a7873aaf0c0964166f4b37b7db51b6/wrapt-1.15.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3"}, + {url = "https://files.pythonhosted.org/packages/29/41/f05bf85417473cf6fe4eec7396c63762e5a457a42102bd1b8af059af6586/wrapt-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd"}, + {url = "https://files.pythonhosted.org/packages/2b/fb/c31489631bb94ac225677c1090f787a4ae367614b5277f13dbfde24b2b69/wrapt-1.15.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975"}, + {url = "https://files.pythonhosted.org/packages/2d/47/16303c59a890696e1a6fd82ba055fc4e0f793fb4815b5003f1f85f7202ce/wrapt-1.15.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7"}, + {url = "https://files.pythonhosted.org/packages/2e/ce/90dcde9ff9238689f111f07b46da2db570252445a781ea147ff668f651b0/wrapt-1.15.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2"}, + {url = "https://files.pythonhosted.org/packages/31/e6/6ac59c5570a7b9aaecb10de39f70dacd0290620330277e60b29edcf8bc9a/wrapt-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2"}, + {url = "https://files.pythonhosted.org/packages/39/ee/2b8d608f2bcf86242daadf5b0b746c11d3657b09892345f10f171b5ca3ac/wrapt-1.15.0-cp35-cp35m-win32.whl", hash = "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559"}, + {url = "https://files.pythonhosted.org/packages/44/a1/40379212a0b678f995fdb4f4f28aeae5724f3212cdfbf97bee8e6fba3f1b/wrapt-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420"}, + {url = "https://files.pythonhosted.org/packages/45/90/a959fa50084d7acc2e628f093c9c2679dd25085aa5085a22592e028b3e06/wrapt-1.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46"}, + {url = "https://files.pythonhosted.org/packages/47/dd/bee4d33058656c0b2e045530224fcddd9492c354af5d20499e5261148dcb/wrapt-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317"}, + {url = "https://files.pythonhosted.org/packages/48/65/0061e7432ca4b635e96e60e27e03a60ddaca3aeccc30e7415fed0325c3c2/wrapt-1.15.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec"}, + {url = "https://files.pythonhosted.org/packages/4a/7b/c63103817bd2f3b0145608ef642ce90d8b6d1e5780d218bce92e93045e06/wrapt-1.15.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653"}, + {url = "https://files.pythonhosted.org/packages/50/eb/af864a01300878f69b4949f8381ad57d5519c1791307e9fd0bc7f5ab50a5/wrapt-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29"}, + {url = "https://files.pythonhosted.org/packages/54/21/282abeb456f22d93533b2d373eeb393298a30b0cb0683fa8a4ed77654273/wrapt-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1"}, + {url = "https://files.pythonhosted.org/packages/55/20/90f5affc2c879db408124ce14b9443b504f961e47a517dff4f24a00df439/wrapt-1.15.0-cp38-cp38-win32.whl", hash = "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b"}, + {url = "https://files.pythonhosted.org/packages/5d/c4/3cc25541ec0404dd1d178e7697a34814d77be1e489cd6f8cb055ac688314/wrapt-1.15.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98"}, + {url = "https://files.pythonhosted.org/packages/65/be/3ae5afe9d78d97595b28914fa7e375ebc6329549d98f02768d5a08f34937/wrapt-1.15.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e"}, + {url = "https://files.pythonhosted.org/packages/6b/b0/bde5400fdf6d18cb7ef527831de0f86ac206c4da1670b67633e5a547b05f/wrapt-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72"}, + {url = "https://files.pythonhosted.org/packages/78/f2/106d90140a93690eab240fae76759d62dae639fcec1bd098eccdb83aa38f/wrapt-1.15.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a"}, + {url = "https://files.pythonhosted.org/packages/7f/b6/6dc0ddacd20337b4ce6ab0d6b0edc7da3898f85c4f97df7f30267e57509e/wrapt-1.15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0"}, + {url = "https://files.pythonhosted.org/packages/81/1e/0bb8f01c6ac5baba66ef1ab65f4644bede856c3c7aede18c896be222151c/wrapt-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba"}, + {url = "https://files.pythonhosted.org/packages/88/f1/4dfaa1ad111d2a48429dca133e46249922ee2f279e9fdd4ab5b149cd6c71/wrapt-1.15.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90"}, + {url = "https://files.pythonhosted.org/packages/8a/1c/740c3ad1b7754dd7213f4df09ccdaf6b19e36da5ff3a269444ba9e103f1b/wrapt-1.15.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9"}, + {url = "https://files.pythonhosted.org/packages/8f/87/ba6dc86e8edb28fd1e314446301802751bd3157e9780385c9eef633994b9/wrapt-1.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e"}, + {url = "https://files.pythonhosted.org/packages/94/55/91dd3a7efbc1db2b07bbfc490d48e8484852c355d55e61e8b1565d7725f6/wrapt-1.15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7"}, + {url = "https://files.pythonhosted.org/packages/96/37/a33c1220e8a298ab18eb070b6a59e4ccc3f7344b434a7ac4bd5d4bdccc97/wrapt-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee"}, + {url = "https://files.pythonhosted.org/packages/9b/50/383c155a05e3e0361d209e3f55ec823f3736c7a46b29923ea33ab85e8d70/wrapt-1.15.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29"}, + {url = "https://files.pythonhosted.org/packages/9d/40/fee1288d654c80fe1bc5ecee1c8d58f761a39bb30c73f1ce106701dd8b0a/wrapt-1.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e"}, + {url = "https://files.pythonhosted.org/packages/a2/3e/ee671ac60945154dfa3a406b8cb5cef2e3b4fa31c7d04edeb92716342026/wrapt-1.15.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0"}, + {url = "https://files.pythonhosted.org/packages/a4/af/8552671e4e7674fcae14bd3976dd9dc6a2b7294730e4a9a94597ac292a21/wrapt-1.15.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248"}, + {url = "https://files.pythonhosted.org/packages/a6/32/f4868adc994648fac4cfe347bcc1381c9afcb1602c8ba0910f36b96c5449/wrapt-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e"}, + {url = "https://files.pythonhosted.org/packages/a7/da/04883b14284c437eac98c7ad2959197f02acbabd57d5ea8ff4893a7c3920/wrapt-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0"}, + {url = "https://files.pythonhosted.org/packages/a9/64/886e512f438f12424b48a3ab23ae2583ec633be6e13eb97b0ccdff8e328a/wrapt-1.15.0-cp310-cp310-win32.whl", hash = "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1"}, + {url = "https://files.pythonhosted.org/packages/aa/24/bbd64ee4e1db9c75ec2a9677c538866f81800bcd2a8abd1a383369369cf5/wrapt-1.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c"}, + {url = "https://files.pythonhosted.org/packages/af/23/cf5dbfd676480fa8fc6eecc4c413183cd8e14369321c5111fec5c12550e9/wrapt-1.15.0-cp39-cp39-win32.whl", hash = "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff"}, + {url = "https://files.pythonhosted.org/packages/af/7f/25913aacbe0c2c68e7354222bdefe4e840489725eb835e311c581396f91f/wrapt-1.15.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f"}, + {url = "https://files.pythonhosted.org/packages/b1/8b/f4c02cf1f841dede987f93c37d42256dc4a82cd07173ad8a5458eee1c412/wrapt-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e"}, + {url = "https://files.pythonhosted.org/packages/b2/b0/a56b129822568d9946e009e8efd53439b9dd38cc1c4af085aa44b2485b40/wrapt-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1"}, + {url = "https://files.pythonhosted.org/packages/b6/0c/435198dbe6961c2343ca725be26b99c8aee615e32c45bc1cb2a960b06183/wrapt-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364"}, + {url = "https://files.pythonhosted.org/packages/b7/3d/9d3cd75f7fc283b6e627c9b0e904189c41ca144185fd8113a1a094dec8ca/wrapt-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86"}, + {url = "https://files.pythonhosted.org/packages/b9/40/975fbb1ab03fa987900bacc365645c4cbead22baddd273b4f5db7f9843d2/wrapt-1.15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c"}, + {url = "https://files.pythonhosted.org/packages/bd/47/57ffe222af59fae1eb56bca7d458b704a9b59380c47f0921efb94dc4786a/wrapt-1.15.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6"}, + {url = "https://files.pythonhosted.org/packages/c3/12/5fabf0014a0f30eb3975b7199ac2731215a40bc8273083f6a89bd6cadec6/wrapt-1.15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc"}, + {url = "https://files.pythonhosted.org/packages/c4/e3/01f879f8e7c1221c72dbd4bfa106624ee3d01cb8cbc82ef57fbb95880cac/wrapt-1.15.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb"}, + {url = "https://files.pythonhosted.org/packages/c7/cd/18d95465323f29e3f3fd3ff84f7acb402a6a61e6caf994dced7140d78f85/wrapt-1.15.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd"}, + {url = "https://files.pythonhosted.org/packages/ca/1c/5caf61431705b3076ca1152abfd6da6304697d7d4fe48bb3448a6decab40/wrapt-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb"}, + {url = "https://files.pythonhosted.org/packages/cd/a0/84b8fe24af8d7f7374d15e0da1cd5502fff59964bbbf34982df0ca2c9047/wrapt-1.15.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f"}, + {url = "https://files.pythonhosted.org/packages/cd/f0/060add4fcb035024f84fb3b5523fb2b119ac08608af3f61dbdda38477900/wrapt-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6"}, + {url = "https://files.pythonhosted.org/packages/cf/b1/3c24fc0f6b589ad8c99cfd1cd3e586ef144e16aaf9381ed952d047a7ee54/wrapt-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475"}, + {url = "https://files.pythonhosted.org/packages/d1/74/3c99ce16947f7af901f6203ab4a3d0908c4db06e800571dabfe8525fa925/wrapt-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752"}, + {url = "https://files.pythonhosted.org/packages/d2/60/9fe25f4cd910ae94e75a1fd4772b058545e107a82629a5ca0f2cd7cc34d5/wrapt-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e"}, + {url = "https://files.pythonhosted.org/packages/d7/4b/1bd4837362d31d402b9bc1a27cdd405baf994dbf9942696f291d2f7eeb73/wrapt-1.15.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8"}, + {url = "https://files.pythonhosted.org/packages/dd/42/9eedee19435dfc0478cdb8bdc71800aab15a297d1074f1aae0d9489adbc3/wrapt-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705"}, + {url = "https://files.pythonhosted.org/packages/dd/e9/85e780a6b70191114b13b129867cec2fab84279f6beb788e130a26e4ca58/wrapt-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079"}, + {url = "https://files.pythonhosted.org/packages/dd/eb/389f9975a6be31ddd19d29128a11f1288d07b624e464598a4b450f8d007e/wrapt-1.15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29"}, + {url = "https://files.pythonhosted.org/packages/de/77/e2ebfa2f46c19094888a364fdb59aeab9d3336a3ad7ccdf542de572d2a35/wrapt-1.15.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094"}, + {url = "https://files.pythonhosted.org/packages/e8/86/fc38e58843159bdda745258d872b1187ad916087369ec57ef93f5e832fa8/wrapt-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7"}, + {url = "https://files.pythonhosted.org/packages/ec/f4/f84538a367105f0a7e507f0c6766d3b15b848fd753647bbf0c206399b322/wrapt-1.15.0-cp311-cp311-win32.whl", hash = "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416"}, + {url = "https://files.pythonhosted.org/packages/ee/25/83f5dcd9f96606521da2d0e7a03a18800264eafb59b569ff109c4d2fea67/wrapt-1.15.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92"}, + {url = "https://files.pythonhosted.org/packages/f6/89/bf77b063c594795aaa056cac7b467463702f346d124d46d7f06e76e8cd97/wrapt-1.15.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8"}, + {url = "https://files.pythonhosted.org/packages/f6/d3/3c6bd4db883537c40eb9d41d738d329d983d049904f708267f3828a60048/wrapt-1.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a"}, + {url = "https://files.pythonhosted.org/packages/f8/49/10013abe31f6892ae57c5cc260f71b7e08f1cc00f0d7b2bcfa482ea74349/wrapt-1.15.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b"}, + {url = "https://files.pythonhosted.org/packages/f8/7d/73e4e3cdb2c780e13f9d87dc10488d7566d8fd77f8d68f0e416bfbd144c7/wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"}, + {url = "https://files.pythonhosted.org/packages/f8/f8/e068dafbb844c1447c55b23c921f3d338cddaba4ea53187a7dd0058452d9/wrapt-1.15.0-py3-none-any.whl", hash = "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640"}, + {url = "https://files.pythonhosted.org/packages/fb/2d/b6fd53b7dbf94d542866cbf1021b9a62595177fc8405fd75e0a5bf3fa3b8/wrapt-1.15.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418"}, + {url = "https://files.pythonhosted.org/packages/fb/bd/ca7fd05a45e7022f3b780a709bbdb081a6138d828ecdb5b7df113a3ad3be/wrapt-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727"}, + {url = "https://files.pythonhosted.org/packages/fd/8a/db55250ad0b536901173d737781e3b5a7cc7063c46b232c2e3a82a33c032/wrapt-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145"}, + {url = "https://files.pythonhosted.org/packages/ff/f6/c044dec6bec4ce64fbc92614c5238dd432780b06293d2efbcab1a349629c/wrapt-1.15.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019"}, +] +"zipp 3.16.2" = [ + {url = "https://files.pythonhosted.org/packages/8c/08/d3006317aefe25ea79d3b76c9650afabaf6d63d1c8443b236e7405447503/zipp-3.16.2-py3-none-any.whl", hash = "sha256:679e51dd4403591b2d6838a48de3d283f3d188412a9782faadf845f298736ba0"}, + {url = "https://files.pythonhosted.org/packages/e2/45/f3b987ad5bf9e08095c1ebe6352238be36f25dd106fde424a160061dce6d/zipp-3.16.2.tar.gz", hash = "sha256:ebc15946aa78bd63458992fc81ec3b6f7b1e92d51c35e6de1c3804e73b799147"}, +] diff --git a/datajunction-query/pyproject.toml b/datajunction-query/pyproject.toml new file mode 100644 index 000000000..8ac861306 --- /dev/null +++ b/datajunction-query/pyproject.toml @@ -0,0 +1,85 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.pdm.build] +includes = ["djqs"] + +[project] +name = "datajunction-query" +dynamic = ["version"] +description = "OSS Implementation of a DataJunction Query Service" +authors = [ + {name = "DataJunction Authors", email = "roberto@dealmeida.net"}, +] +dependencies = [ + "importlib-metadata", + "SQLAlchemy-Utils>=0.37.7", + "accept-types==0.4.1", + "cachelib>=0.4.0", + "duckdb==0.8.1", + "duckdb-engine", + "fastapi>=0.79.0", + "msgpack>=1.0.3", + "python-dotenv==0.19.2", + "requests<=2.29.0,>=2.28.2", + "rich>=10.16.2", + "sqlalchemy<2.0.0,>=1.4.41", + "sqlmodel<1.0.0,>=0.0.8", + "sqlparse<1.0.0,>=0.4.3", + "pyspark==3.3.2", +] +requires-python = ">=3.8,<4.0" +readme = "README.rst" +license = {text = "MIT"} +classifiers = [ + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", +] + +[project.optional-dependencies] +uvicorn = [ + "uvicorn[standard]>=0.21.1", +] + +[tool.hatch.version] +path = "djqs/__about__.py" + +[project.urls] +repository = "https://github.com/DataJunction/dj" + +[tool.pdm.dev-dependencies] +test = [ + "alembic>=1.7.7", + "codespell>=2.1.0", + "freezegun>=1.1.0", + "pre-commit>=3.2.2", + "pyfakefs>=4.5.1", + "pylint>=2.15.3", + "pytest-asyncio==0.15.1", + "pytest-cov>=2.12.1", + "pytest-integration==0.2.2", + "pytest-mock>=3.6.1", + "pytest>=6.2.5", + "requests-mock>=1.9.3", + "setuptools>=49.6.0", + "pip-tools>=6.4.0", + "pydruid>=0.6.4", + "typing-extensions>=4.3.0", + "httpx>=0.24.1", +] + +[tool.hatch.metadata] +allow-direct-references = true + +[tool.coverage.run] +source = ['djqs/'] + +[tool.isort] +src_paths = ["djqs/", "tests/"] +profile = 'black' diff --git a/datajunction-query/scripts/generate-openapi.py b/datajunction-query/scripts/generate-openapi.py new file mode 100755 index 000000000..ef420e609 --- /dev/null +++ b/datajunction-query/scripts/generate-openapi.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# pylint: skip-file + +import argparse +import json + +from djqs.api.main import app + + +def save_openapi_spec(f: str): + spec = app.openapi() + with open(f, "w") as outfile: + outfile.write(json.dumps(spec, indent=4)) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Generate a file containing the OpenAPI spec for a DJ server", + ) + parser.add_argument( + "-o", + "--output-file", + dest="filename", + required=True, + metavar="FILE", + ) + args = vars(parser.parse_args()) + save_openapi_spec(f=args["filename"]) diff --git a/datajunction-query/setup.cfg b/datajunction-query/setup.cfg new file mode 100644 index 000000000..4e278c7e1 --- /dev/null +++ b/datajunction-query/setup.cfg @@ -0,0 +1,19 @@ +# This file is used to configure your project. +# Read more about the various options under: +# https://setuptools.pypa.io/en/latest/userguide/declarative_config.html +# https://setuptools.pypa.io/en/latest/references/keywords.html + +[tool:pytest] +# Specify command line options as you would do when invoking pytest directly. +# e.g. --cov-report html (or xml) for html/xml output or --junitxml junit.xml +# in order to write a coverage file that can be read by Jenkins. +# CAUTION: --cov flags may prohibit setting breakpoints while debugging. +# Comment those flags to avoid this pytest issue. +addopts = + --cov djqs --cov-report term-missing + --verbose +norecursedirs = + dist + build + .tox +testpaths = tests diff --git a/datajunction-query/tests/__init__.py b/datajunction-query/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/datajunction-query/tests/api/__init__.py b/datajunction-query/tests/api/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/datajunction-query/tests/api/catalogs_test.py b/datajunction-query/tests/api/catalogs_test.py new file mode 100644 index 000000000..129aeed82 --- /dev/null +++ b/datajunction-query/tests/api/catalogs_test.py @@ -0,0 +1,366 @@ +""" +Tests for the catalog API. +""" + +from fastapi.testclient import TestClient + + +def test_catalog_adding_a_new_catalog( + client: TestClient, +) -> None: + """ + Test adding a catalog + """ + response = client.post( + "/catalogs/", + json={ + "name": "dev", + }, + ) + data = response.json() + assert response.status_code == 201 + assert data == {"name": "dev", "engines": []} + + +def test_catalog_list( + client: TestClient, +) -> None: + """ + Test listing catalogs + """ + response = client.post( + "/engines/", + json={ + "name": "foo", + "version": "1.0", + "uri": "bar", + }, + ) + assert response.status_code == 201 + + response = client.post( + "/catalogs/", + json={ + "name": "dev", + "engines": [ + { + "name": "foo", + "version": "1.0", + "uri": "bar", + }, + ], + }, + ) + assert response.status_code == 201 + + response = client.post( + "/catalogs/", + json={ + "name": "test", + }, + ) + assert response.status_code == 201 + + response = client.post( + "/catalogs/", + json={ + "name": "prod", + }, + ) + assert response.status_code == 201 + + response = client.get("/catalogs/") + assert response.status_code == 200 + assert response.json() == [ + { + "name": "dev", + "engines": [{"name": "foo", "version": "1.0"}], + }, + {"name": "test", "engines": []}, + {"name": "prod", "engines": []}, + ] + + +def test_catalog_get_catalog( + client: TestClient, +) -> None: + """ + Test getting a catalog + """ + response = client.post( + "/engines/", + json={ + "name": "foo", + "version": "1.0", + "uri": "bar", + }, + ) + assert response.status_code == 201 + + response = client.post( + "/catalogs/", + json={ + "name": "dev", + "engines": [ + { + "name": "foo", + "version": "1.0", + "uri": "bar", + }, + ], + }, + ) + assert response.status_code == 201 + + response = client.get( + "/catalogs/dev", + ) + assert response.status_code == 200 + data = response.json() + assert data == { + "name": "dev", + "engines": [{"name": "foo", "version": "1.0"}], + } + + +def test_catalog_adding_a_new_catalog_with_engines( + client: TestClient, +) -> None: + """ + Test adding a catalog with engines + """ + response = client.post( + "/engines/", + json={ + "name": "foo", + "uri": "bar", + "version": "1.0", + }, + ) + data = response.json() + assert response.status_code == 201 + + response = client.post( + "/catalogs/", + json={ + "name": "dev", + "engines": [ + { + "name": "foo", + "version": "1.0", + "uri": "bar", + }, + ], + }, + ) + data = response.json() + assert response.status_code == 201 + assert data == { + "name": "dev", + "engines": [ + { + "name": "foo", + "version": "1.0", + }, + ], + } + + +def test_catalog_adding_a_new_catalog_then_attaching_engines( + client: TestClient, +) -> None: + """ + Test adding a catalog then attaching a catalog + """ + response = client.post( + "/engines/", + json={ + "name": "foo", + "uri": "bar", + "version": "1.0", + }, + ) + data = response.json() + assert response.status_code == 201 + + response = client.post( + "/catalogs/", + json={ + "name": "dev", + }, + ) + assert response.status_code == 201 + + client.post( + "/catalogs/dev/engines/", + json=[ + { + "name": "foo", + "version": "1.0", + }, + ], + ) + + response = client.get("/catalogs/dev/") + data = response.json() + assert data == { + "name": "dev", + "engines": [ + { + "name": "foo", + "version": "1.0", + }, + ], + } + + +def test_catalog_adding_without_duplicating( + client: TestClient, +) -> None: + """ + Test adding a catalog and having existing catalogs not re-added + """ + response = client.post( + "/engines/", + json={ + "name": "foo", + "uri": "bar", + "version": "2.4.4", + }, + ) + data = response.json() + assert response.status_code == 201 + + response = client.post( + "/engines/", + json={ + "name": "foo", + "version": "3.3.0", + "uri": "bar", + }, + ) + data = response.json() + assert response.status_code == 201 + + response = client.post( + "/engines/", + json={ + "name": "foo", + "version": "1.0", + "uri": "bar", + }, + ) + data = response.json() + assert response.status_code == 201 + + response = client.post( + "/catalogs/", + json={ + "name": "dev", + }, + ) + assert response.status_code == 201 + + response = client.post( + "/catalogs/dev/engines/", + json=[ + { + "name": "foo", + "version": "2.4.4", + }, + { + "name": "foo", + "version": "3.3.0", + }, + { + "name": "foo", + "version": "1.0", + }, + ], + ) + assert response.status_code == 201 + + response = client.post( + "/catalogs/dev/engines/", + json=[ + { + "name": "foo", + "version": "2.4.4", + }, + { + "name": "foo", + "version": "3.3.0", + }, + { + "name": "foo", + "version": "1.0", + }, + ], + ) + assert response.status_code == 201 + data = response.json() + assert data == { + "name": "dev", + "engines": [ + { + "name": "foo", + "version": "2.4.4", + }, + { + "name": "foo", + "version": "3.3.0", + }, + { + "name": "foo", + "version": "1.0", + }, + ], + } + + +def test_catalog_raise_on_adding_a_new_catalog_with_nonexistent_engines( + client: TestClient, +) -> None: + """ + Test raising an error when adding a catalog with engines that do not exist + """ + response = client.post( + "/catalogs/", + json={ + "name": "dev", + "engines": [ + { + "name": "foo", + "version": "2.0", + }, + ], + }, + ) + data = response.json() + assert response.status_code == 404 + assert data == {"detail": "Engine not found: `foo` version `2.0`"} + + +def test_catalog_raise_on_catalog_already_exists( + client: TestClient, +) -> None: + """ + Test raise on catalog already exists + """ + response = client.post( + "/catalogs/", + json={ + "name": "dev", + }, + ) + assert response.status_code == 201 + + response = client.post( + "/catalogs/", + json={ + "name": "dev", + }, + ) + data = response.json() + assert response.status_code == 409 + assert data == {"detail": "Catalog already exists: `dev`"} diff --git a/datajunction-query/tests/api/engines_test.py b/datajunction-query/tests/api/engines_test.py new file mode 100644 index 000000000..53c6fe571 --- /dev/null +++ b/datajunction-query/tests/api/engines_test.py @@ -0,0 +1,131 @@ +""" +Tests for the engine API. +""" + +from fastapi.testclient import TestClient + + +def test_engine_adding_a_new_engine( + client: TestClient, +) -> None: + """ + Test adding an engine + """ + response = client.post( + "/engines/", + json={ + "name": "foo", + "version": "1.0", + "uri": "bar", + }, + ) + data = response.json() + assert response.status_code == 201 + assert data == {"name": "foo", "version": "1.0"} + + +def test_engine_list( + client: TestClient, +) -> None: + """ + Test listing engines + """ + response = client.post( + "/engines/", + json={"name": "foo", "version": "1.0", "uri": "bar"}, + ) + assert response.status_code == 201 + + response = client.post( + "/engines/", + json={ + "name": "foo", + "version": "1.1", + "uri": "baz", + }, + ) + assert response.status_code == 201 + + response = client.post( + "/engines/", + json={ + "name": "foo", + "version": "1.2", + "uri": "qux", + }, + ) + assert response.status_code == 201 + + response = client.get("/engines/") + assert response.status_code == 200 + data = response.json() + assert data == [ + { + "name": "foo", + "uri": "bar", + "version": "1.0", + }, + { + "name": "foo", + "uri": "baz", + "version": "1.1", + }, + { + "name": "foo", + "uri": "qux", + "version": "1.2", + }, + ] + + +def test_engine_get_engine( + client: TestClient, +) -> None: + """ + Test getting an engine + """ + response = client.post( + "/engines/", + json={ + "name": "foo", + "version": "1.0", + "uri": "bar", + }, + ) + assert response.status_code == 201 + + response = client.get( + "/engines/foo/1.0", + ) + assert response.status_code == 200 + data = response.json() + assert data == {"name": "foo", "version": "1.0"} + + +def test_engine_raise_on_engine_already_exists( + client: TestClient, +) -> None: + """ + Test raise on engine already exists + """ + response = client.post( + "/engines/", + json={ + "name": "foo", + "version": "1.0", + "uri": "bar", + }, + ) + assert response.status_code == 201 + + response = client.post( + "/engines/", + json={ + "name": "foo", + "version": "1.0", + "uri": "bar", + }, + ) + assert response.status_code == 409 + data = response.json() + assert data == {"detail": "Engine already exists: `foo` version `1.0`"} diff --git a/datajunction-query/tests/api/queries_test.py b/datajunction-query/tests/api/queries_test.py new file mode 100644 index 000000000..07f555fc9 --- /dev/null +++ b/datajunction-query/tests/api/queries_test.py @@ -0,0 +1,669 @@ +""" +Tests for the queries API. +""" + +import datetime +import json +from http import HTTPStatus +from unittest import mock + +import msgpack +from fastapi.testclient import TestClient +from freezegun import freeze_time +from pytest_mock import MockerFixture +from sqlmodel import Session + +from djqs.config import Settings +from djqs.engine import describe_table_via_spark, process_query +from djqs.models.catalog import Catalog +from djqs.models.engine import Engine +from djqs.models.query import ( + Query, + QueryCreate, + QueryState, + Results, + StatementResults, + decode_results, + encode_results, +) + + +def test_submit_query(session: Session, client: TestClient) -> None: + """ + Test ``POST /queries/``. + """ + engine = Engine(name="test_engine", version="1.0", uri="sqlite://") + catalog = Catalog(name="test_catalog", engines=[engine]) + session.add(catalog) + session.commit() + session.refresh(catalog) + + query_create = QueryCreate( + catalog_name=catalog.name, + engine_name=engine.name, + engine_version=engine.version, + submitted_query="SELECT 1 AS col", + ) + payload = query_create.json(by_alias=True) + assert payload == json.dumps( + { + "catalog_name": "test_catalog", + "engine_name": "test_engine", + "engine_version": "1.0", + "submitted_query": "SELECT 1 AS col", + "async_": False, + }, + ) + + with freeze_time("2021-01-01T00:00:00Z"): + response = client.post( + "/queries/", + data=payload, + headers={"Content-Type": "application/json", "Accept": "application/json"}, + ) + data = response.json() + + assert response.status_code == 200 + assert data["catalog_name"] == "test_catalog" + assert data["engine_name"] == "test_engine" + assert data["engine_version"] == "1.0" + assert data["submitted_query"] == "SELECT 1 AS col" + assert data["executed_query"] == "SELECT 1 AS col" + assert data["scheduled"] == "2021-01-01T00:00:00" + assert data["started"] == "2021-01-01T00:00:00" + assert data["finished"] == "2021-01-01T00:00:00" + assert data["state"] == "FINISHED" + assert data["progress"] == 1.0 + assert len(data["results"]) == 1 + assert data["results"][0]["sql"] == "SELECT 1 AS col" + assert data["results"][0]["columns"] == [{"name": "col", "type": "STR"}] + assert data["results"][0]["rows"] == [[1]] + assert data["errors"] == [] + + +def test_submit_query_msgpack(session: Session, client: TestClient) -> None: + """ + Test ``POST /queries/`` using msgpack. + """ + engine = Engine(name="test_engine", version="1.0", uri="sqlite://") + catalog = Catalog(name="test_catalog", engines=[engine]) + session.add(catalog) + session.commit() + session.refresh(catalog) + + query_create = QueryCreate( + catalog_name=catalog.name, + engine_name=engine.name, + engine_version=engine.version, + submitted_query="SELECT 1 AS col", + ) + payload = query_create.dict(by_alias=True) + data = msgpack.packb(payload, default=encode_results) + + with freeze_time("2021-01-01T00:00:00Z"): + response = client.post( + "/queries/", + data=data, + headers={ + "Content-Type": "application/msgpack", + "Accept": "application/msgpack; q=1.0, application/json; q=0.5", + }, + ) + data = msgpack.unpackb(response.content, ext_hook=decode_results) + + assert response.headers.get("content-type") == "application/msgpack" + assert response.status_code == 200 + assert data["catalog_name"] == "test_catalog" + assert data["engine_name"] == "test_engine" + assert data["engine_version"] == "1.0" + assert data["submitted_query"] == "SELECT 1 AS col" + assert data["executed_query"] == "SELECT 1 AS col" + assert data["scheduled"] == datetime.datetime(2021, 1, 1) + assert data["started"] == datetime.datetime(2021, 1, 1) + assert data["finished"] == datetime.datetime(2021, 1, 1) + assert data["state"] == "FINISHED" + assert data["progress"] == 1.0 + assert len(data["results"]) == 1 + assert data["results"][0]["sql"] == "SELECT 1 AS col" + assert data["results"][0]["columns"] == [{"name": "col", "type": "STR"}] + assert data["results"][0]["rows"] == [[1]] + assert data["errors"] == [] + + +def test_submit_query_errors( + session: Session, + client: TestClient, +) -> None: + """ + Test ``POST /queries/`` with missing/invalid content type. + """ + engine = Engine(name="test_engine", version="1.0", uri="sqlite://") + catalog = Catalog(name="test_catalog", engines=[engine]) + session.add(catalog) + session.commit() + session.refresh(catalog) + + query_create = QueryCreate( + catalog_name=catalog.name, + engine_name=engine.name, + engine_version=engine.version, + submitted_query="SELECT 1 AS col", + ) + payload = query_create.json(by_alias=True) + + response = client.post( + "/queries/", + data=payload, + headers={"Accept": "application/json"}, + ) + assert response.status_code == 400 + assert response.json() == {"detail": "Content type must be specified"} + + response = client.post( + "/queries/", + data=payload, + headers={ + "Content-Type": "application/protobuf", + "Accept": "application/json", + }, + ) + assert response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY + assert response.json() == { + "detail": "Content type not accepted: application/protobuf", + } + + response = client.post( + "/queries/", + data=payload, + headers={"Content-Type": "application/json", "Accept": "application/protobuf"}, + ) + assert response.status_code == 406 + assert response.json() == { + "detail": "Client MUST accept: application/json, application/msgpack", + } + + +def test_submit_query_multiple_statements(session: Session, client: TestClient) -> None: + """ + Test ``POST /queries/``. + """ + engine = Engine(name="test_engine", version="1.0", uri="sqlite://") + catalog = Catalog(name="test_catalog", engines=[engine]) + session.add(catalog) + session.commit() + session.refresh(catalog) + + query_create = QueryCreate( + catalog_name=catalog.name, + engine_name=engine.name, + engine_version=engine.version, + submitted_query="SELECT 1 AS col; SELECT 2 AS another_col", + ) + + with freeze_time("2021-01-01T00:00:00Z"): + response = client.post( + "/queries/", + data=query_create.json(), + headers={"Content-Type": "application/json", "Accept": "application/json"}, + ) + data = response.json() + + assert response.status_code == 200 + assert data["catalog_name"] == "test_catalog" + assert data["engine_name"] == "test_engine" + assert data["engine_version"] == "1.0" + assert data["submitted_query"] == "SELECT 1 AS col; SELECT 2 AS another_col" + assert data["executed_query"] == "SELECT 1 AS col; SELECT 2 AS another_col" + assert data["scheduled"] == "2021-01-01T00:00:00" + assert data["started"] == "2021-01-01T00:00:00" + assert data["finished"] == "2021-01-01T00:00:00" + assert data["state"] == "FINISHED" + assert data["progress"] == 1.0 + assert len(data["results"]) == 2 + assert data["results"][0]["sql"] == "SELECT 1 AS col" + assert data["results"][0]["columns"] == [{"name": "col", "type": "STR"}] + assert data["results"][0]["rows"] == [[1]] + assert data["results"][1]["sql"] == "SELECT 2 AS another_col" + assert data["results"][1]["columns"] == [{"name": "another_col", "type": "STR"}] + assert data["results"][1]["rows"] == [[2]] + assert data["errors"] == [] + + +def test_submit_query_results_backend( + session: Session, + settings: Settings, + client: TestClient, +) -> None: + """ + Test that ``POST /queries/`` stores results. + """ + engine = Engine(name="test_engine", version="1.0", uri="sqlite://") + catalog = Catalog(name="test_catalog", engines=[engine]) + session.add(catalog) + session.commit() + session.refresh(catalog) + + query_create = QueryCreate( + catalog_name=catalog.name, + engine_name=engine.name, + engine_version=engine.version, + submitted_query="SELECT 1 AS col", + ) + + with freeze_time("2021-01-01T00:00:00Z"): + response = client.post( + "/queries/", + data=query_create.json(), + headers={"Content-Type": "application/json", "Accept": "application/json"}, + ) + data = response.json() + assert data == { + "catalog_name": "test_catalog", + "engine_name": "test_engine", + "engine_version": "1.0", + "id": mock.ANY, + "submitted_query": "SELECT 1 AS col", + "executed_query": "SELECT 1 AS col", + "scheduled": "2021-01-01T00:00:00", + "started": "2021-01-01T00:00:00", + "finished": "2021-01-01T00:00:00", + "state": "FINISHED", + "progress": 1.0, + "results": [ + { + "sql": "SELECT 1 AS col", + "columns": [{"name": "col", "type": "STR"}], + "rows": [[1]], + "row_count": 1, + }, + ], + "next": None, + "previous": None, + "errors": [], + } + cached = settings.results_backend.get(data["id"]) + assert json.loads(cached) == [ + { + "sql": "SELECT 1 AS col", + "columns": [{"name": "col", "type": "STR"}], + "rows": [[1]], + "row_count": 1, + }, + ] + + +def test_submit_query_async( + mocker: MockerFixture, + session: Session, + client: TestClient, +) -> None: + """ + Test ``POST /queries/`` on an async database. + """ + add_task = mocker.patch("fastapi.BackgroundTasks.add_task") + + engine = Engine(name="test_engine", version="1.0", uri="sqlite://") + catalog = Catalog(name="test_catalog", engines=[engine]) + session.add(catalog) + session.commit() + session.refresh(catalog) + + query_create = QueryCreate( + catalog_name=catalog.name, + engine_name=engine.name, + engine_version=engine.version, + submitted_query="SELECT 1 AS col", + async_=True, + ) + + with freeze_time("2021-01-01T00:00:00Z", auto_tick_seconds=300): + response = client.post( + "/queries/", + data=query_create.json(), + headers={"Content-Type": "application/json", "Accept": "application/json"}, + ) + data = response.json() + + assert response.status_code == 201 + assert data["catalog_name"] == "test_catalog" + assert data["engine_name"] == "test_engine" + assert data["engine_version"] == "1.0" + assert data["submitted_query"] == "SELECT 1 AS col" + assert data["executed_query"] is None + assert data["scheduled"] is None + assert data["started"] is None + assert data["finished"] is None + assert data["state"] == "ACCEPTED" + assert data["progress"] == 0.0 + assert data["results"] == [] + assert data["errors"] == [] + + # check that ``BackgroundTasks.add_task`` was called + add_task.assert_called() + arguments = add_task.mock_calls[0].args + assert arguments[0] == process_query # pylint: disable=comparison-with-callable + assert arguments[1] == session + assert isinstance(arguments[2], Settings) + assert isinstance(arguments[3], Query) + + +def test_submit_query_error(session: Session, client: TestClient) -> None: + """ + Test submitting invalid query to ``POST /queries/``. + """ + engine = Engine(name="test_engine", version="1.0", uri="sqlite://") + catalog = Catalog(name="test_catalog", engines=[engine]) + session.add(catalog) + session.commit() + session.refresh(catalog) + + query_create = QueryCreate( + catalog_name=catalog.name, + engine_name=engine.name, + engine_version=engine.version, + submitted_query="SELECT FROM", + async_=False, + ) + + response = client.post( + "/queries/", + data=query_create.json(), + headers={"Content-Type": "application/json", "Accept": "application/json"}, + ) + data = response.json() + + assert response.status_code == 200 + assert data["catalog_name"] == "test_catalog" + assert data["engine_name"] == "test_engine" + assert data["engine_version"] == "1.0" + assert data["submitted_query"] == "SELECT FROM" + assert data["executed_query"] == "SELECT FROM" + assert data["state"] == "FAILED" + assert data["progress"] == 0.0 + assert data["results"] == [] + assert data["errors"] == [ + '(sqlite3.OperationalError) near "FROM": syntax error\n' + "[SQL: SELECT FROM]\n" + "(Background on this error at: https://sqlalche.me/e/14/e3q8)", + ] + + +def test_read_query(session: Session, settings: Settings, client: TestClient) -> None: + """ + Test ``GET /queries/{query_id}``. + """ + engine = Engine(name="test_engine", version="1.0", uri="sqlite://") + catalog = Catalog(name="test_catalog", engines=[engine]) + session.add(catalog) + session.commit() + session.refresh(catalog) + + query = Query( + catalog_name=catalog.name, + engine_name=engine.name, + engine_version=engine.version, + submitted_query="SELECT 1", + executed_query="SELECT 1", + state=QueryState.RUNNING, + progress=0.5, + async_=False, + ) + session.add(query) + session.commit() + session.refresh(query) + + results = Results( + __root__=[ + StatementResults( + sql="SELECT 1", + columns=[{"name": "col", "type": "STR"}], + rows=[[1]], + ), + ], + ) + settings.results_backend.add(str(query.id), results.json()) + + response = client.get(f"/queries/{query.id}") + data = response.json() + + assert response.status_code == 200 + assert data["catalog_name"] == "test_catalog" + assert data["engine_name"] == "test_engine" + assert data["engine_version"] == "1.0" + assert data["submitted_query"] == "SELECT 1" + assert data["executed_query"] == "SELECT 1" + assert data["state"] == "RUNNING" + assert data["progress"] == 0.5 + assert len(data["results"]) == 1 + assert data["results"][0]["sql"] == "SELECT 1" + assert data["results"][0]["columns"] == [{"name": "col", "type": "STR"}] + assert data["results"][0]["rows"] == [[1]] + assert data["errors"] == [] + + response = client.get("/queries/27289db6-a75c-47fc-b451-da59a743a168") + assert response.status_code == 404 + + response = client.get("/queries/123") + assert response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY + + +def test_read_query_no_results_backend(session: Session, client: TestClient) -> None: + """ + Test ``GET /queries/{query_id}``. + """ + engine = Engine(name="test_engine", version="1.0", uri="sqlite://") + catalog = Catalog(name="test_catalog", engines=[engine]) + session.add(catalog) + session.commit() + session.refresh(catalog) + + query = Query( + catalog_name=catalog.name, + engine_name=engine.name, + engine_version=engine.version, + submitted_query="SELECT 1", + executed_query="SELECT 1", + state=QueryState.RUNNING, + progress=0.5, + async_=False, + ) + session.add(query) + session.commit() + session.refresh(query) + + response = client.get(f"/queries/{query.id}") + data = response.json() + + assert response.status_code == 200 + assert data["catalog_name"] == "test_catalog" + assert data["engine_name"] == "test_engine" + assert data["engine_version"] == "1.0" + assert data["submitted_query"] == "SELECT 1" + assert data["executed_query"] == "SELECT 1" + assert data["state"] == "RUNNING" + assert data["progress"] == 0.5 + assert data["results"] == [] + assert data["errors"] == [] + + response = client.get("/queries/27289db6-a75c-47fc-b451-da59a743a168") + assert response.status_code == 404 + + response = client.get("/queries/123") + + +def test_submit_spark_query(session: Session, client: TestClient) -> None: + """ + Test submitting a Spark query + """ + engine = Engine(name="test_spark_engine", version="3.3.2", uri="spark://local[*]") + catalog = Catalog(name="test_catalog", engines=[engine]) + session.add(catalog) + session.commit() + session.refresh(catalog) + + query_create = QueryCreate( + catalog_name=catalog.name, + engine_name=engine.name, + engine_version=engine.version, + submitted_query="SELECT 1 AS int_col, 'a' as str_col", + ) + payload = query_create.json(by_alias=True) + assert payload == json.dumps( + { + "catalog_name": "test_catalog", + "engine_name": "test_spark_engine", + "engine_version": "3.3.2", + "submitted_query": "SELECT 1 AS int_col, 'a' as str_col", + "async_": False, + }, + ) + + with freeze_time("2021-01-01T00:00:00Z"): + response = client.post( + "/queries/", + data=payload, + headers={"Content-Type": "application/json", "Accept": "application/json"}, + ) + data = response.json() + + assert response.status_code == 200 + assert data["catalog_name"] == "test_catalog" + assert data["engine_name"] == "test_spark_engine" + assert data["engine_version"] == "3.3.2" + assert data["submitted_query"] == "SELECT 1 AS int_col, 'a' as str_col" + assert data["executed_query"] == "SELECT 1 AS int_col, 'a' as str_col" + assert data["scheduled"] == "2021-01-01T00:00:00" + assert data["started"] == "2021-01-01T00:00:00" + assert data["finished"] == "2021-01-01T00:00:00" + assert data["state"] == "FINISHED" + assert data["progress"] == 1.0 + assert len(data["results"]) == 1 + assert data["results"][0]["sql"] == "SELECT 1 AS int_col, 'a' as str_col" + assert data["results"][0]["columns"] == [] + assert data["results"][0]["rows"] == [[1, "a"]] + assert data["errors"] == [] + + +def test_spark_fixture_show_tables(spark) -> None: + """ + Test that show tables of the spark fixture lists the roads tables + """ + spark_df = spark.sql("show tables") + records = spark_df.rdd.map(tuple).collect() + assert records == [ + ("", "contractors", True), + ("", "dispatchers", True), + ("", "hard_hat_state", True), + ("", "hard_hats", True), + ("", "municipality", True), + ("", "municipality_municipality_type", True), + ("", "municipality_type", True), + ("", "repair_order_details", True), + ("", "repair_orders", True), + ("", "repair_type", True), + ("", "us_region", True), + ("", "us_states", True), + ] + + +def test_spark_describe_tables(spark) -> None: + """ + Test that using spark to describe tables works + """ + column_metadata = describe_table_via_spark(spark, None, "contractors") + assert column_metadata == [ + {"name": "contractor_id", "type": "int"}, + {"name": "company_name", "type": "string"}, + {"name": "contact_name", "type": "string"}, + {"name": "contact_title", "type": "string"}, + {"name": "address", "type": "string"}, + {"name": "city", "type": "string"}, + {"name": "state", "type": "string"}, + {"name": "postal_code", "type": "string"}, + {"name": "country", "type": "string"}, + {"name": "phone", "type": "string"}, + ] + + +def test_spark_fixture_query(spark) -> None: + """ + Test a spark query against the roads database in the spark fixture + """ + spark_df = spark.sql( + """ + SELECT ro.repair_order_id, ro.order_date + FROM repair_orders ro + LEFT JOIN repair_order_details roi + ON ro.repair_order_id = roi.repair_order_id + ORDER BY ro.repair_order_id + LIMIT 5 + """, + ) + records = spark_df.rdd.map(tuple).collect() + assert records == [ + (10001, datetime.date(2007, 7, 4)), + (10002, datetime.date(2007, 7, 5)), + (10003, datetime.date(2007, 7, 8)), + (10004, datetime.date(2007, 7, 8)), + (10005, datetime.date(2007, 7, 9)), + ] + + +@mock.patch("djqs.engine.duckdb.connect") +def test_submit_duckdb_query( + mock_duckdb_connect, + session: Session, + client: TestClient, + duckdb_conn, +) -> None: + """ + Test submitting a Spark query + """ + mock_duckdb_connect.return_value = duckdb_conn + engine = Engine(name="test_duckdb_engine", version="0.7.1", uri="duckdb://local[*]") + catalog = Catalog(name="test_catalog", engines=[engine]) + session.add(catalog) + session.commit() + session.refresh(catalog) + + query_create = QueryCreate( + catalog_name=catalog.name, + engine_name=engine.name, + engine_version=engine.version, + submitted_query="SELECT 1 AS int_col, 'a' as str_col", + ) + payload = query_create.json(by_alias=True) + assert payload == json.dumps( + { + "catalog_name": "test_catalog", + "engine_name": "test_duckdb_engine", + "engine_version": "0.7.1", + "submitted_query": "SELECT 1 AS int_col, 'a' as str_col", + "async_": False, + }, + ) + + with freeze_time("2021-01-01T00:00:00Z"): + response = client.post( + "/queries/", + data=payload, + headers={"Content-Type": "application/json", "Accept": "application/json"}, + ) + data = response.json() + + assert response.status_code == 200 + assert data["catalog_name"] == "test_catalog" + assert data["engine_name"] == "test_duckdb_engine" + assert data["engine_version"] == "0.7.1" + assert data["submitted_query"] == "SELECT 1 AS int_col, 'a' as str_col" + assert data["executed_query"] == "SELECT 1 AS int_col, 'a' as str_col" + assert data["scheduled"] == "2021-01-01T00:00:00" + assert data["started"] == "2021-01-01T00:00:00" + assert data["finished"] == "2021-01-01T00:00:00" + assert data["state"] == "FINISHED" + assert data["progress"] == 1.0 + assert len(data["results"]) == 1 + assert data["results"][0]["sql"] == "SELECT 1 AS int_col, 'a' as str_col" + assert data["results"][0]["columns"] == [] + assert data["results"][0]["rows"] == [[1, "a"]] + assert data["errors"] == [] diff --git a/datajunction-query/tests/api/table_test.py b/datajunction-query/tests/api/table_test.py new file mode 100644 index 000000000..8bcb9d755 --- /dev/null +++ b/datajunction-query/tests/api/table_test.py @@ -0,0 +1,61 @@ +""" +Tests for the catalog API. +""" +from fastapi.testclient import TestClient + + +def test_table_columns(client: TestClient, mocker): + """ + Test getting table columns + """ + response = client.post( + "/engines/", + json={"name": "default", "version": "", "uri": "sqlite://"}, + ) + assert response.status_code == 201 + columns = [ + {"name": "col_a", "type": "STR"}, + {"name": "col_b", "type": "INT"}, + {"name": "col_c", "type": "MAP"}, + {"name": "col_d", "type": "STR"}, + {"name": "col_e", "type": "DECIMAL"}, + ] + mocker.patch("djqs.api.tables.get_columns", return_value=columns) + response = client.get("/table/foo.bar.baz/columns/") + assert response.json() == { + "name": "foo.bar.baz", + "columns": [ + {"name": "col_a", "type": "STR"}, + {"name": "col_b", "type": "INT"}, + {"name": "col_c", "type": "MAP"}, + {"name": "col_d", "type": "STR"}, + {"name": "col_e", "type": "DECIMAL"}, + ], + } + + +def test_raise_on_invalid_table_name(client: TestClient): + """ + Test raising on invalid table names + """ + response = client.get("/table/foo.bar.baz.qux/columns/") + assert response.json() == { + "message": ( + "The provided table value `foo.bar.baz.qux` is invalid. " + "A valid value for `table` must be in the format " + "`..
`" + ), + "errors": [], + "warnings": [], + } + + response = client.get("/table/foo/columns/") + assert response.json() == { + "message": ( + "The provided table value `foo` is invalid. " + "A valid value for `table` must be in the format " + "`..
`" + ), + "errors": [], + "warnings": [], + } diff --git a/examples/configs/databases/druid.yaml b/datajunction-query/tests/configs/databases/druid.yaml similarity index 50% rename from examples/configs/databases/druid.yaml rename to datajunction-query/tests/configs/databases/druid.yaml index 09dfa20aa..5f57d4e60 100644 --- a/examples/configs/databases/druid.yaml +++ b/datajunction-query/tests/configs/databases/druid.yaml @@ -1,3 +1,4 @@ description: An Apache Druid database -URI: druid://localhost:8082/druid/v2/sql/ +URI: druid://druid_broker:8082/druid/v2/sql/ read_only: true +cost: 1 diff --git a/datajunction-query/tests/configs/databases/gsheets.yaml b/datajunction-query/tests/configs/databases/gsheets.yaml new file mode 100644 index 000000000..24a8c7104 --- /dev/null +++ b/datajunction-query/tests/configs/databases/gsheets.yaml @@ -0,0 +1,8 @@ +description: A Google Sheets connector +URI: gsheets:// +extra_params: + catalog: + comments: https://docs.google.com/spreadsheets/d/1SkEZOipqjXQnxHLMr2kZ7Tbn7OiHSgO99gOCS5jTQJs/edit#gid=1811447072 + users: https://docs.google.com/spreadsheets/d/1SkEZOipqjXQnxHLMr2kZ7Tbn7OiHSgO99gOCS5jTQJs/edit#gid=0 +read_only: true +cost: 100 diff --git a/datajunction-query/tests/configs/databases/postgres.yaml b/datajunction-query/tests/configs/databases/postgres.yaml new file mode 100644 index 000000000..a113a21cd --- /dev/null +++ b/datajunction-query/tests/configs/databases/postgres.yaml @@ -0,0 +1,7 @@ +description: A Postgres database +URI: postgresql://username:FoolishPassword@postgres_examples:5432/examples +extra_params: + connect_args: + sslmode: prefer +read_only: false +cost: 10 diff --git a/datajunction-query/tests/configs/nodes/core/comments.yaml b/datajunction-query/tests/configs/nodes/core/comments.yaml new file mode 100644 index 000000000..b6ef4f1a4 --- /dev/null +++ b/datajunction-query/tests/configs/nodes/core/comments.yaml @@ -0,0 +1,28 @@ +description: A fact table with comments +type: source +columns: + id: + type: INT + user_id: + type: INT + dimension: core.dim_users + timestamp: + type: DATETIME + text: + type: STR +tables: + gsheets: + - catalog: null + schema: null + table: comments + cost: 100.0 + postgres: + - catalog: null + schema: public + table: comments + cost: 10.0 + druid: + - catalog: null + schema: druid + table: comments + cost: 1.0 diff --git a/datajunction-query/tests/configs/nodes/core/dim_users.yaml b/datajunction-query/tests/configs/nodes/core/dim_users.yaml new file mode 100644 index 000000000..92cb012a0 --- /dev/null +++ b/datajunction-query/tests/configs/nodes/core/dim_users.yaml @@ -0,0 +1,18 @@ +description: User dimension +type: dimension +query: SELECT * FROM core.users +columns: + id: + type: INT + full_name: + type: STR + age: + type: INT + country: + type: STR + gender: + type: STR + preferred_language: + type: STR + secret_number: + type: FLOAT diff --git a/datajunction-query/tests/configs/nodes/core/num_comments.yaml b/datajunction-query/tests/configs/nodes/core/num_comments.yaml new file mode 100644 index 000000000..4bb80c2a2 --- /dev/null +++ b/datajunction-query/tests/configs/nodes/core/num_comments.yaml @@ -0,0 +1,6 @@ +description: Number of comments +type: metric +query: SELECT COUNT(*) FROM core.comments +columns: + _col0: + type: INT diff --git a/datajunction-query/tests/configs/nodes/core/users.yaml b/datajunction-query/tests/configs/nodes/core/users.yaml new file mode 100644 index 000000000..bdef7cd68 --- /dev/null +++ b/datajunction-query/tests/configs/nodes/core/users.yaml @@ -0,0 +1,28 @@ +description: A user table +type: source +columns: + id: + type: INT + full_name: + type: STR + age: + type: INT + country: + type: STR + gender: + type: STR + preferred_language: + type: STR + secret_number: + type: FLOAT +tables: + gsheets: + - catalog: null + schema: null + table: users + cost: 100.0 + postgres: + - catalog: null + schema: public + table: dim_users + cost: 10.0 diff --git a/datajunction-query/tests/conftest.py b/datajunction-query/tests/conftest.py new file mode 100644 index 000000000..3a333b495 --- /dev/null +++ b/datajunction-query/tests/conftest.py @@ -0,0 +1,103 @@ +""" +Fixtures for testing. +""" +# pylint: disable=redefined-outer-name, invalid-name + +from pathlib import Path +from typing import Iterator + +import duckdb +import pytest +from cachelib.simple import SimpleCache +from fastapi.testclient import TestClient +from pyspark.sql import SparkSession +from pytest_mock import MockerFixture +from sqlmodel import Session, SQLModel, create_engine +from sqlmodel.pool import StaticPool + +from djqs.api.main import app +from djqs.config import Settings +from djqs.utils import get_session, get_settings + + +@pytest.fixture +def settings(mocker: MockerFixture) -> Iterator[Settings]: + """ + Custom settings for unit tests. + """ + settings = Settings( + index="sqlite://", + results_backend=SimpleCache(default_timeout=0), + ) + + mocker.patch( + "djqs.utils.get_settings", + return_value=settings, + ) + + yield settings + + +@pytest.fixture() +def session() -> Iterator[Session]: + """ + Create an in-memory SQLite session to test models. + """ + engine = create_engine( + "sqlite://", + connect_args={"check_same_thread": False}, + poolclass=StaticPool, + ) + SQLModel.metadata.create_all(engine) + + with Session(engine, autoflush=False) as session: + yield session + + +@pytest.fixture() +def client(session: Session, settings: Settings) -> Iterator[TestClient]: + """ + Create a client for testing APIs. + """ + + def get_session_override() -> Session: + return session + + def get_settings_override() -> Settings: + return settings + + app.dependency_overrides[get_session] = get_session_override + app.dependency_overrides[get_settings] = get_settings_override + + with TestClient(app) as client: + yield client + + app.dependency_overrides.clear() + + +@pytest.fixture(scope="session") +def spark(): + """ + A spark session fixture preloaded with the roads example database + """ + spark = ( + SparkSession.builder.master("local[*]") + .appName("djqs-tests") + .enableHiveSupport() + .getOrCreate() + ) + for filepath in Path("tests/resources").glob("*"): + spark.read.parquet( + str(filepath), + header=True, + inferSchema=True, + ).createOrReplaceTempView(Path(filepath).stem) + yield spark + + +@pytest.fixture(scope="session") +def duckdb_conn(): + """ + A duckdb connection to a roads database + """ + return duckdb.connect(database="docker/default.duckdb") diff --git a/datajunction-query/tests/exceptions_test.py b/datajunction-query/tests/exceptions_test.py new file mode 100644 index 000000000..097d97c4e --- /dev/null +++ b/datajunction-query/tests/exceptions_test.py @@ -0,0 +1,44 @@ +""" +Tests errors. +""" + +from http import HTTPStatus + +from djqs.exceptions import DJError, DJException, ErrorCode + + +def test_dj_exception() -> None: + """ + Test the base ``DJException``. + """ + exc = DJException() + assert exc.dbapi_exception == "Error" + assert exc.http_status_code == 500 + + exc = DJException(dbapi_exception="InternalError") + assert exc.dbapi_exception == "InternalError" + assert exc.http_status_code == 500 + + exc = DJException( + dbapi_exception="ProgrammingError", + http_status_code=HTTPStatus.BAD_REQUEST, + ) + assert exc.dbapi_exception == "ProgrammingError" + assert exc.http_status_code == HTTPStatus.BAD_REQUEST + + exc = DJException("Message") + assert str(exc) == "Message" + exc = DJException( + "Message", + errors=[ + DJError(message="Error 1", code=ErrorCode.UNKWNON_ERROR), + DJError(message="Error 2", code=ErrorCode.UNKWNON_ERROR), + ], + ) + assert ( + str(exc) + == """Message +The following errors happened: +- Error 1 (error code: 0) +- Error 2 (error code: 0)""" + ) diff --git a/datajunction-query/tests/resources/contractors.parquet b/datajunction-query/tests/resources/contractors.parquet new file mode 100644 index 000000000..436668e6c Binary files /dev/null and b/datajunction-query/tests/resources/contractors.parquet differ diff --git a/datajunction-query/tests/resources/dispatchers.parquet b/datajunction-query/tests/resources/dispatchers.parquet new file mode 100644 index 000000000..356f719ec Binary files /dev/null and b/datajunction-query/tests/resources/dispatchers.parquet differ diff --git a/datajunction-query/tests/resources/hard_hat_state.parquet b/datajunction-query/tests/resources/hard_hat_state.parquet new file mode 100644 index 000000000..59a5b654b Binary files /dev/null and b/datajunction-query/tests/resources/hard_hat_state.parquet differ diff --git a/datajunction-query/tests/resources/hard_hats.parquet b/datajunction-query/tests/resources/hard_hats.parquet new file mode 100644 index 000000000..d6bea8e90 Binary files /dev/null and b/datajunction-query/tests/resources/hard_hats.parquet differ diff --git a/datajunction-query/tests/resources/municipality.parquet b/datajunction-query/tests/resources/municipality.parquet new file mode 100644 index 000000000..08eed043b Binary files /dev/null and b/datajunction-query/tests/resources/municipality.parquet differ diff --git a/datajunction-query/tests/resources/municipality_municipality_type.parquet b/datajunction-query/tests/resources/municipality_municipality_type.parquet new file mode 100644 index 000000000..13c513e25 Binary files /dev/null and b/datajunction-query/tests/resources/municipality_municipality_type.parquet differ diff --git a/datajunction-query/tests/resources/municipality_type.parquet b/datajunction-query/tests/resources/municipality_type.parquet new file mode 100644 index 000000000..297cc25f4 Binary files /dev/null and b/datajunction-query/tests/resources/municipality_type.parquet differ diff --git a/datajunction-query/tests/resources/repair_order_details.parquet b/datajunction-query/tests/resources/repair_order_details.parquet new file mode 100644 index 000000000..265f2a8bb Binary files /dev/null and b/datajunction-query/tests/resources/repair_order_details.parquet differ diff --git a/datajunction-query/tests/resources/repair_orders.parquet b/datajunction-query/tests/resources/repair_orders.parquet new file mode 100644 index 000000000..703362916 Binary files /dev/null and b/datajunction-query/tests/resources/repair_orders.parquet differ diff --git a/datajunction-query/tests/resources/repair_type.parquet b/datajunction-query/tests/resources/repair_type.parquet new file mode 100644 index 000000000..c065d0d45 Binary files /dev/null and b/datajunction-query/tests/resources/repair_type.parquet differ diff --git a/datajunction-query/tests/resources/us_region.parquet b/datajunction-query/tests/resources/us_region.parquet new file mode 100644 index 000000000..08e31fa29 Binary files /dev/null and b/datajunction-query/tests/resources/us_region.parquet differ diff --git a/datajunction-query/tests/resources/us_states.parquet b/datajunction-query/tests/resources/us_states.parquet new file mode 100644 index 000000000..a1f4dd92a Binary files /dev/null and b/datajunction-query/tests/resources/us_states.parquet differ diff --git a/datajunction-query/tests/utils_test.py b/datajunction-query/tests/utils_test.py new file mode 100644 index 000000000..258565e7d --- /dev/null +++ b/datajunction-query/tests/utils_test.py @@ -0,0 +1,59 @@ +""" +Tests for ``djqs.utils``. +""" + +import logging + +import pytest +from pytest_mock import MockerFixture +from sqlalchemy.engine.url import make_url + +from djqs.config import Settings +from djqs.utils import get_metadata_engine, get_session, get_settings, setup_logging + + +def test_setup_logging() -> None: + """ + Test ``setup_logging``. + """ + setup_logging("debug") + assert logging.root.level == logging.DEBUG + + with pytest.raises(ValueError) as excinfo: + setup_logging("invalid") + assert str(excinfo.value) == "Invalid log level: invalid" + + +def test_get_session(mocker: MockerFixture) -> None: + """ + Test ``get_session``. + """ + mocker.patch("djqs.utils.get_metadata_engine") + Session = mocker.patch("djqs.utils.Session") # pylint: disable=invalid-name + + session = next(get_session()) + + assert session == Session().__enter__.return_value + + +def test_get_settings(mocker: MockerFixture) -> None: + """ + Test ``get_settings``. + """ + mocker.patch("djqs.utils.load_dotenv") + Settings = mocker.patch( # pylint: disable=invalid-name, redefined-outer-name + "djqs.utils.Settings", + ) + + # should be already cached, since it's called by the Celery app + get_settings() + Settings.assert_not_called() + + +def test_get_metadata_engine(mocker: MockerFixture, settings: Settings) -> None: + """ + Test ``get_metadata_engine``. + """ + mocker.patch("djqs.utils.get_settings", return_value=settings) + engine = get_metadata_engine() + assert engine.url == make_url("sqlite://") diff --git a/datajunction-query/tox.ini b/datajunction-query/tox.ini new file mode 100644 index 000000000..f6c338a21 --- /dev/null +++ b/datajunction-query/tox.ini @@ -0,0 +1,14 @@ +[tox] +envlist = py3 + +[testenv] +pip_pre = true +deps = + -rrequirements/test.txt + pytest + testfixtures + coverage +commands = + pip install -e .[testing] + coverage run --source djqs --parallel-mode -m pytest {posargs} --without-integration --without-slow-integration + coverage html --fail-under 100 -d test-reports/{envname}/coverage-html diff --git a/datajunction-reflection/.coveragerc b/datajunction-reflection/.coveragerc new file mode 100644 index 000000000..d5c993481 --- /dev/null +++ b/datajunction-reflection/.coveragerc @@ -0,0 +1,31 @@ +# .coveragerc to control coverage.py +[run] +branch = True +source = djrs +# omit = bad_file.py + +[paths] +source = + djrs/ + */site-packages/ + +[report] +sort = -Cover +# Regexes for lines to exclude from consideration +exclude_lines = + # Have to re-enable the standard pragma + pragma: no cover + + # Don't complain about missing debug-only code: + def __repr__ + if self\.debug + + # Don't complain if tests don't hit defensive assertion code: + raise AssertionError + raise NotImplementedError + + # Don't complain if non-runnable code isn't run: + if 0: + if __name__ == .__main__.: + + if TYPE_CHECKING: diff --git a/datajunction-reflection/.flake8 b/datajunction-reflection/.flake8 new file mode 100644 index 000000000..d9ad0b409 --- /dev/null +++ b/datajunction-reflection/.flake8 @@ -0,0 +1,5 @@ +[flake8] +ignore = E203, E266, E501, W503, F403, F401 +max-line-length = 79 +max-complexity = 18 +select = B,C,E,F,W,T4,B9 diff --git a/datajunction-reflection/.gitignore b/datajunction-reflection/.gitignore new file mode 100644 index 000000000..b6e47617d --- /dev/null +++ b/datajunction-reflection/.gitignore @@ -0,0 +1,129 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ diff --git a/datajunction-reflection/.isort.cfg b/datajunction-reflection/.isort.cfg new file mode 100644 index 000000000..c00191213 --- /dev/null +++ b/datajunction-reflection/.isort.cfg @@ -0,0 +1,3 @@ +[settings] +profile = black +known_first_party = djrs diff --git a/datajunction-reflection/.pre-commit-config.yaml b/datajunction-reflection/.pre-commit-config.yaml new file mode 100644 index 000000000..2b35bcedc --- /dev/null +++ b/datajunction-reflection/.pre-commit-config.yaml @@ -0,0 +1,100 @@ +files: ^datajunction-reflection/ + +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.1.0 + hooks: + - id: trailing-whitespace + - id: check-added-large-files + - id: check-ast + exclude: ^templates/ + - id: check-json + - id: check-merge-conflict + - id: check-xml + - id: check-yaml + - id: debug-statements + exclude: ^templates/ + - id: end-of-file-fixer + exclude: openapi.json + - id: requirements-txt-fixer + exclude: ^templates/ + - id: mixed-line-ending + args: ['--fix=auto'] # replace 'auto' with 'lf' to enforce Linux/Mac line endings or 'crlf' for Windows + +## If you want to avoid flake8 errors due to unused vars or imports: +# - repo: https://github.com/myint/autoflake.git +# rev: v1.4 +# hooks: +# - id: autoflake +# args: [ +# --in-place, +# --remove-all-unused-imports, +# --remove-unused-variables, +# ] + +- repo: https://github.com/pycqa/isort + rev: 5.12.0 + hooks: + - id: isort + +- repo: https://github.com/psf/black + rev: 22.8.0 + hooks: + - id: black + language_version: python3 + exclude: ^templates/ + +## If like to embrace black styles even in the docs: +# - repo: https://github.com/asottile/blacken-docs +# rev: v1.9.1 +# hooks: +# - id: blacken-docs +# additional_dependencies: [black] + +- repo: https://github.com/PyCQA/flake8 + rev: 3.9.2 + hooks: + - id: flake8 + exclude: ^templates/ + ## You can add flake8 plugins via `additional_dependencies`: + # additional_dependencies: [flake8-bugbear] + +- repo: https://github.com/pre-commit/mirrors-mypy + rev: 'v0.931' # Use the sha / tag you want to point at + hooks: + - id: mypy + exclude: ^templates/ + additional_dependencies: + - types-requests + - types-freezegun + - types-python-dateutil + - types-pkg_resources + - types-PyYAML + - types-tabulate +- repo: https://github.com/asottile/add-trailing-comma + rev: v2.2.1 + hooks: + - id: add-trailing-comma +#- repo: https://github.com/asottile/reorder_python_imports +# rev: v2.5.0 +# hooks: +# - id: reorder-python-imports +# args: [--application-directories=.:src] +- repo: https://github.com/hadialqattan/pycln + rev: v2.1.7 # Possible releases: https://github.com/hadialqattan/pycln/tags + hooks: + - id: pycln + args: [--config=pyproject.toml] + exclude: ^templates/ +- repo: local + hooks: + - id: pylint + name: pylint + entry: pylint --disable=duplicate-code,use-implicit-booleaness-not-comparison + language: system + types: [python] + exclude: ^templates/ +- repo: https://github.com/pdm-project/pdm + rev: 2.6.1 + hooks: + - id: pdm-lock-check diff --git a/datajunction-reflection/.pylintrc b/datajunction-reflection/.pylintrc new file mode 100644 index 000000000..5b6fccaa9 --- /dev/null +++ b/datajunction-reflection/.pylintrc @@ -0,0 +1,6 @@ +[MESSAGES CONTROL] + +[MASTER] +# https://github.com/samuelcolvin/pydantic/issues/1961#issuecomment-759522422 +extension-pkg-whitelist=pydantic +ignore=templates,docs diff --git a/datajunction-reflection/CODE_OF_CONDUCT.md b/datajunction-reflection/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..18c914718 --- /dev/null +++ b/datajunction-reflection/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/datajunction-reflection/Dockerfile b/datajunction-reflection/Dockerfile new file mode 100644 index 000000000..549a63b54 --- /dev/null +++ b/datajunction-reflection/Dockerfile @@ -0,0 +1,4 @@ +FROM python:3.10 +WORKDIR /code +COPY . /code +RUN pip install -e . diff --git a/datajunction-reflection/LICENSE b/datajunction-reflection/LICENSE new file mode 100644 index 000000000..208e34b3d --- /dev/null +++ b/datajunction-reflection/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 DJ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/datajunction-reflection/Makefile b/datajunction-reflection/Makefile new file mode 100644 index 000000000..9258e39d8 --- /dev/null +++ b/datajunction-reflection/Makefile @@ -0,0 +1,29 @@ +pyenv: .python-version + +.python-version: setup.cfg + if [ -z "`pyenv virtualenvs | grep djrs`" ]; then\ + pyenv virtualenv 3.10 djrs;\ + fi + if [ ! -f .python-version ]; then\ + pyenv local djrs;\ + fi + pdm install + touch .python-version + +docker-build: + docker build . + +test: pyenv + pdm run pytest --cov=datajunction_reflection -vv tests/ --doctest-modules datajunction_reflection --without-integration --without-slow-integration ${PYTEST_ARGS} + +integration: pyenv + pdm run pytest --cov=datajunction_reflection -vv tests/ --doctest-modules datajunction_reflection --with-integration --with-slow-integration + +clean: + pyenv virtualenv-delete djrs + +spellcheck: + codespell -L froms -S "*.json" datajunction_reflection docs/*rst tests templates + +check: + pdm run pre-commit run --all-files diff --git a/datajunction-reflection/README.md b/datajunction-reflection/README.md new file mode 100644 index 000000000..d94696f3a --- /dev/null +++ b/datajunction-reflection/README.md @@ -0,0 +1,9 @@ +# DJ Reflection Service + +The reflection service polls the DJ core service for all nodes with associated tables, whether source +tables or materialized tables. For each node, it refreshes the node's schema based on the associated +table's schema that it retrieves from the query service. It also retrieves the available partitions and +the valid through timestamp of these tables and reflects them accordingly to DJ core. + +This service uses a celery beat scheduler, with a configurable polling interval that defaults to once per +hour and async tasks for each node's reflection. diff --git a/datajunction-reflection/datajunction_reflection/__about__.py b/datajunction-reflection/datajunction_reflection/__about__.py new file mode 100644 index 000000000..3fdaaa2ff --- /dev/null +++ b/datajunction-reflection/datajunction_reflection/__about__.py @@ -0,0 +1,4 @@ +""" +Version for Hatch +""" +__version__ = "0.0.1a1" diff --git a/datajunction-reflection/datajunction_reflection/__init__.py b/datajunction-reflection/datajunction_reflection/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/datajunction-reflection/datajunction_reflection/config.py b/datajunction-reflection/datajunction_reflection/config.py new file mode 100644 index 000000000..f291a753f --- /dev/null +++ b/datajunction-reflection/datajunction_reflection/config.py @@ -0,0 +1,26 @@ +"""Reflection service settings.""" +from functools import lru_cache + +from pydantic import BaseSettings + + +class Settings(BaseSettings): + """ + Default settings for the reflection service. + """ + + core_service: str = "http://dj:8000" + query_service: str = "http://djqs:8001" + celery_broker: str = "redis://djrs-redis:6379/1" + celery_results_backend: str = "redis://djrs-redis:6379/2" + + # Set the number of seconds to wait in between polling + polling_interval: int = 3600 + + +@lru_cache +def get_settings() -> Settings: + """ + Return a cached settings object. + """ + return Settings() diff --git a/datajunction-reflection/datajunction_reflection/worker/__init__.py b/datajunction-reflection/datajunction_reflection/worker/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/datajunction-reflection/datajunction_reflection/worker/app.py b/datajunction-reflection/datajunction_reflection/worker/app.py new file mode 100644 index 000000000..ddfc523b5 --- /dev/null +++ b/datajunction-reflection/datajunction_reflection/worker/app.py @@ -0,0 +1,7 @@ +""" +Celery app that does the polling of nodes in DJ and then subsequent +queueing of reflection tasks. +""" +from datajunction_reflection.worker.utils import get_celery + +celery_app = get_celery() diff --git a/datajunction-reflection/datajunction_reflection/worker/tasks.py b/datajunction-reflection/datajunction_reflection/worker/tasks.py new file mode 100644 index 000000000..8b1339b3e --- /dev/null +++ b/datajunction-reflection/datajunction_reflection/worker/tasks.py @@ -0,0 +1,81 @@ +"""Reflection service celery tasks.""" +from abc import ABC + +import celery +import requests +from celery import shared_task +from celery.utils.log import get_task_logger + +from datajunction_reflection.worker.app import celery_app +from datajunction_reflection.worker.utils import get_settings + +logger = get_task_logger(__name__) + + +class ReflectionServiceTask(celery.Task, ABC): + """ + Base reflection service task. + """ + + abstract = True + + def on_failure(self, exc, task_id, *args, **kwargs): + logger.exception("%s failed: %s", task_id, exc) # pragma: no cover + + +@shared_task( + queue="celery", + name="datajunction_reflection.worker.app.refresh", + base=ReflectionServiceTask, +) +def refresh(): + """ + Find available DJ nodes and kick off reflection tasks for + nodes with associated tables. + """ + settings = get_settings() + response = requests.get( + f"{settings.core_service}/nodes/?node_type=source", + timeout=30, + ) + response.raise_for_status() + source_nodes = response.json() + + tasks = [] + for node_name in source_nodes: + task = celery_app.send_task( + "datajunction_reflection.worker.tasks.reflect_source", + (node_name,), + ) + tasks.append(task) + + +@shared_task( + queue="celery", + name="datajunction_reflection.worker.tasks.reflect_source", + base=ReflectionServiceTask, +) +def reflect_source( + node_name: str, +): + """ + This reflects the state of the node's associated table, whether + external or materialized, back to the DJ core service. + """ + logger.info(f"Refreshing source node={node_name} in DJ core") + settings = get_settings() + + # Call the source node's refresh endpoint + response = requests.post( + f"{settings.core_service}/nodes/{node_name}/refresh/", + timeout=30, + ) + + logger.info( + "Finished refreshing source node `%s`. Response: %s", + node_name, + response.reason, + ) + + # pylint: disable=fixme + # TODO: Post actual availability state when information available diff --git a/datajunction-reflection/datajunction_reflection/worker/utils.py b/datajunction-reflection/datajunction_reflection/worker/utils.py new file mode 100644 index 000000000..16555ffcc --- /dev/null +++ b/datajunction-reflection/datajunction_reflection/worker/utils.py @@ -0,0 +1,41 @@ +"""Utility functions for retrieving API clients.""" +import os + +from celery import Celery + +from datajunction_reflection.config import get_settings + + +def get_celery() -> Celery: + """ + core celery app + """ + + settings = get_settings() + + celery_app = Celery( + __name__, + include=[ + "datajunction_reflection.worker.app", + "datajunction_reflection.worker.tasks", + ], + ) + celery_app.conf.broker_url = os.environ.get( + "CELERY_BROKER_URL", + settings.celery_broker, + ) + celery_app.conf.result_backend = os.environ.get( + "CELERY_RESULT_BACKEND", + settings.celery_results_backend, + ) + celery_app.conf.imports = [ + "datajunction_reflection.worker.app", + "datajunction_reflection.worker.tasks", + ] + celery_app.conf.beat_schedule = { + "refresh": { + "task": "datajunction_reflection.worker.app.refresh", + "schedule": settings.polling_interval, + }, + } + return celery_app diff --git a/datajunction-reflection/pdm.lock b/datajunction-reflection/pdm.lock new file mode 100644 index 000000000..dc66a8cce --- /dev/null +++ b/datajunction-reflection/pdm.lock @@ -0,0 +1,1276 @@ +# This file is @generated by PDM. +# It is not intended for manual editing. + +[metadata] +groups = ["default", "test"] +cross_platform = true +static_urls = false +lock_version = "4.2" +content_hash = "sha256:b691c0cb73bc65dcb812d3e361a3c78c0b4d9e20406c4ed47b04f8edb9e271b9" + +[[package]] +name = "amqp" +version = "5.1.1" +requires_python = ">=3.6" +summary = "Low-level AMQP client for Python (fork of amqplib)." +dependencies = [ + "vine>=5.0.0", +] +files = [ + {file = "amqp-5.1.1-py3-none-any.whl", hash = "sha256:6f0956d2c23d8fa6e7691934d8c3930eadb44972cbbd1a7ae3a520f735d43359"}, + {file = "amqp-5.1.1.tar.gz", hash = "sha256:2c1b13fecc0893e946c65cbd5f36427861cffa4ea2201d8f6fca22e2a373b5e2"}, +] + +[[package]] +name = "astroid" +version = "2.15.6" +requires_python = ">=3.7.2" +summary = "An abstract syntax tree for Python with inference support." +dependencies = [ + "lazy-object-proxy>=1.4.0", + "typing-extensions>=4.0.0; python_version < \"3.11\"", + "wrapt<2,>=1.11; python_version < \"3.11\"", + "wrapt<2,>=1.14; python_version >= \"3.11\"", +] +files = [ + {file = "astroid-2.15.6-py3-none-any.whl", hash = "sha256:389656ca57b6108f939cf5d2f9a2a825a3be50ba9d589670f393236e0a03b91c"}, + {file = "astroid-2.15.6.tar.gz", hash = "sha256:903f024859b7c7687d7a7f3a3f73b17301f8e42dfd9cc9df9d4418172d3e2dbd"}, +] + +[[package]] +name = "async-timeout" +version = "4.0.2" +requires_python = ">=3.6" +summary = "Timeout context manager for asyncio programs" +files = [ + {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, + {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, +] + +[[package]] +name = "backports-zoneinfo" +version = "0.2.1" +requires_python = ">=3.6" +summary = "Backport of the standard library zoneinfo module" +files = [ + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:da6013fd84a690242c310d77ddb8441a559e9cb3d3d59ebac9aca1a57b2e18bc"}, + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:89a48c0d158a3cc3f654da4c2de1ceba85263fafb861b98b59040a5086259722"}, + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:1c5742112073a563c81f786e77514969acb58649bcdf6cdf0b4ed31a348d4546"}, + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win32.whl", hash = "sha256:e8236383a20872c0cdf5a62b554b27538db7fa1bbec52429d8d106effbaeca08"}, + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:8439c030a11780786a2002261569bdf362264f605dfa4d65090b64b05c9f79a7"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:17746bd546106fa389c51dbea67c8b7c8f0d14b5526a579ca6ccf5ed72c526cf"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5c144945a7752ca544b4b78c8c41544cdfaf9786f25fe5ffb10e838e19a27570"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win32.whl", hash = "sha256:e55b384612d93be96506932a786bbcde5a2db7a9e6a4bb4bffe8b733f5b9036b"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a76b38c52400b762e48131494ba26be363491ac4f9a04c1b7e92483d169f6582"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:8961c0f32cd0336fb8e8ead11a1f8cd99ec07145ec2931122faaac1c8f7fd987"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e81b76cace8eda1fca50e345242ba977f9be6ae3945af8d46326d776b4cf78d1"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7b0a64cda4145548fed9efc10322770f929b944ce5cee6c0dfe0c87bf4c0c8c9"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-win32.whl", hash = "sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6"}, + {file = "backports.zoneinfo-0.2.1.tar.gz", hash = "sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2"}, +] + +[[package]] +name = "backports-zoneinfo" +version = "0.2.1" +extras = ["tzdata"] +requires_python = ">=3.6" +summary = "Backport of the standard library zoneinfo module" +dependencies = [ + "backports-zoneinfo==0.2.1", + "tzdata", +] +files = [ + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:da6013fd84a690242c310d77ddb8441a559e9cb3d3d59ebac9aca1a57b2e18bc"}, + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:89a48c0d158a3cc3f654da4c2de1ceba85263fafb861b98b59040a5086259722"}, + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:1c5742112073a563c81f786e77514969acb58649bcdf6cdf0b4ed31a348d4546"}, + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win32.whl", hash = "sha256:e8236383a20872c0cdf5a62b554b27538db7fa1bbec52429d8d106effbaeca08"}, + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:8439c030a11780786a2002261569bdf362264f605dfa4d65090b64b05c9f79a7"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:17746bd546106fa389c51dbea67c8b7c8f0d14b5526a579ca6ccf5ed72c526cf"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5c144945a7752ca544b4b78c8c41544cdfaf9786f25fe5ffb10e838e19a27570"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win32.whl", hash = "sha256:e55b384612d93be96506932a786bbcde5a2db7a9e6a4bb4bffe8b733f5b9036b"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a76b38c52400b762e48131494ba26be363491ac4f9a04c1b7e92483d169f6582"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:8961c0f32cd0336fb8e8ead11a1f8cd99ec07145ec2931122faaac1c8f7fd987"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e81b76cace8eda1fca50e345242ba977f9be6ae3945af8d46326d776b4cf78d1"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7b0a64cda4145548fed9efc10322770f929b944ce5cee6c0dfe0c87bf4c0c8c9"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-win32.whl", hash = "sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6"}, + {file = "backports.zoneinfo-0.2.1.tar.gz", hash = "sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2"}, +] + +[[package]] +name = "billiard" +version = "4.1.0" +requires_python = ">=3.7" +summary = "Python multiprocessing fork with improvements and bugfixes" +files = [ + {file = "billiard-4.1.0-py3-none-any.whl", hash = "sha256:0f50d6be051c6b2b75bfbc8bfd85af195c5739c281d3f5b86a5640c65563614a"}, + {file = "billiard-4.1.0.tar.gz", hash = "sha256:1ad2eeae8e28053d729ba3373d34d9d6e210f6e4d8bf0a9c64f92bd053f1edf5"}, +] + +[[package]] +name = "build" +version = "0.10.0" +requires_python = ">= 3.7" +summary = "A simple, correct Python build frontend" +dependencies = [ + "colorama; os_name == \"nt\"", + "packaging>=19.0", + "pyproject-hooks", + "tomli>=1.1.0; python_version < \"3.11\"", +] +files = [ + {file = "build-0.10.0-py3-none-any.whl", hash = "sha256:af266720050a66c893a6096a2f410989eeac74ff9a68ba194b3f6473e8e26171"}, + {file = "build-0.10.0.tar.gz", hash = "sha256:d5b71264afdb5951d6704482aac78de887c80691c52b88a9ad195983ca2c9269"}, +] + +[[package]] +name = "celery" +version = "5.3.1" +requires_python = ">=3.8" +summary = "Distributed Task Queue." +dependencies = [ + "backports-zoneinfo>=0.2.1; python_version < \"3.9\"", + "billiard<5.0,>=4.1.0", + "click-didyoumean>=0.3.0", + "click-plugins>=1.1.1", + "click-repl>=0.2.0", + "click<9.0,>=8.1.2", + "kombu<6.0,>=5.3.1", + "python-dateutil>=2.8.2", + "tzdata>=2022.7", + "vine<6.0,>=5.0.0", +] +files = [ + {file = "celery-5.3.1-py3-none-any.whl", hash = "sha256:27f8f3f3b58de6e0ab4f174791383bbd7445aff0471a43e99cfd77727940753f"}, + {file = "celery-5.3.1.tar.gz", hash = "sha256:f84d1c21a1520c116c2b7d26593926581191435a03aa74b77c941b93ca1c6210"}, +] + +[[package]] +name = "celery" +version = "5.3.1" +extras = ["pytest"] +requires_python = ">=3.8" +summary = "Distributed Task Queue." +dependencies = [ + "celery==5.3.1", + "pytest-celery==0.0.0", +] +files = [ + {file = "celery-5.3.1-py3-none-any.whl", hash = "sha256:27f8f3f3b58de6e0ab4f174791383bbd7445aff0471a43e99cfd77727940753f"}, + {file = "celery-5.3.1.tar.gz", hash = "sha256:f84d1c21a1520c116c2b7d26593926581191435a03aa74b77c941b93ca1c6210"}, +] + +[[package]] +name = "celery" +version = "5.3.1" +extras = ["redis"] +requires_python = ">=3.8" +summary = "Distributed Task Queue." +dependencies = [ + "celery==5.3.1", + "redis!=4.5.5,>=4.5.2", +] +files = [ + {file = "celery-5.3.1-py3-none-any.whl", hash = "sha256:27f8f3f3b58de6e0ab4f174791383bbd7445aff0471a43e99cfd77727940753f"}, + {file = "celery-5.3.1.tar.gz", hash = "sha256:f84d1c21a1520c116c2b7d26593926581191435a03aa74b77c941b93ca1c6210"}, +] + +[[package]] +name = "certifi" +version = "2023.7.22" +requires_python = ">=3.6" +summary = "Python package for providing Mozilla's CA Bundle." +files = [ + {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, + {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, +] + +[[package]] +name = "cfgv" +version = "3.3.1" +requires_python = ">=3.6.1" +summary = "Validate configuration and produce human readable error messages." +files = [ + {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, + {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.2.0" +requires_python = ">=3.7.0" +summary = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +files = [ + {file = "charset-normalizer-3.2.0.tar.gz", hash = "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-win32.whl", hash = "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-win32.whl", hash = "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-win32.whl", hash = "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-win32.whl", hash = "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-win32.whl", hash = "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80"}, + {file = "charset_normalizer-3.2.0-py3-none-any.whl", hash = "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6"}, +] + +[[package]] +name = "click" +version = "8.1.6" +requires_python = ">=3.7" +summary = "Composable command line interface toolkit" +dependencies = [ + "colorama; platform_system == \"Windows\"", +] +files = [ + {file = "click-8.1.6-py3-none-any.whl", hash = "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5"}, + {file = "click-8.1.6.tar.gz", hash = "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd"}, +] + +[[package]] +name = "click-didyoumean" +version = "0.3.0" +requires_python = ">=3.6.2,<4.0.0" +summary = "Enables git-like *did-you-mean* feature in click" +dependencies = [ + "click>=7", +] +files = [ + {file = "click-didyoumean-0.3.0.tar.gz", hash = "sha256:f184f0d851d96b6d29297354ed981b7dd71df7ff500d82fa6d11f0856bee8035"}, + {file = "click_didyoumean-0.3.0-py3-none-any.whl", hash = "sha256:a0713dc7a1de3f06bc0df5a9567ad19ead2d3d5689b434768a6145bff77c0667"}, +] + +[[package]] +name = "click-plugins" +version = "1.1.1" +summary = "An extension module for click to enable registering CLI commands via setuptools entry-points." +dependencies = [ + "click>=4.0", +] +files = [ + {file = "click-plugins-1.1.1.tar.gz", hash = "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b"}, + {file = "click_plugins-1.1.1-py2.py3-none-any.whl", hash = "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8"}, +] + +[[package]] +name = "click-repl" +version = "0.3.0" +requires_python = ">=3.6" +summary = "REPL plugin for Click" +dependencies = [ + "click>=7.0", + "prompt-toolkit>=3.0.36", +] +files = [ + {file = "click-repl-0.3.0.tar.gz", hash = "sha256:17849c23dba3d667247dc4defe1757fff98694e90fe37474f3feebb69ced26a9"}, + {file = "click_repl-0.3.0-py3-none-any.whl", hash = "sha256:fb7e06deb8da8de86180a33a9da97ac316751c094c6899382da7feeeeb51b812"}, +] + +[[package]] +name = "codespell" +version = "2.2.5" +requires_python = ">=3.7" +summary = "Codespell" +files = [ + {file = "codespell-2.2.5-py3-none-any.whl", hash = "sha256:efa037f54b73c84f7bd14ce8e853d5f822cdd6386ef0ff32e957a3919435b9ec"}, + {file = "codespell-2.2.5.tar.gz", hash = "sha256:6d9faddf6eedb692bf80c9a94ec13ab4f5fb585aabae5f3750727148d7b5be56"}, +] + +[[package]] +name = "colorama" +version = "0.4.6" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +summary = "Cross-platform colored terminal text." +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "coverage" +version = "7.2.7" +requires_python = ">=3.7" +summary = "Code coverage measurement for Python" +files = [ + {file = "coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8"}, + {file = "coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb"}, + {file = "coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6"}, + {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2"}, + {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063"}, + {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1"}, + {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353"}, + {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495"}, + {file = "coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818"}, + {file = "coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850"}, + {file = "coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f"}, + {file = "coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe"}, + {file = "coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3"}, + {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f"}, + {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb"}, + {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833"}, + {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97"}, + {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a"}, + {file = "coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a"}, + {file = "coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562"}, + {file = "coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4"}, + {file = "coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4"}, + {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01"}, + {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6"}, + {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d"}, + {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de"}, + {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d"}, + {file = "coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511"}, + {file = "coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3"}, + {file = "coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f"}, + {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb"}, + {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9"}, + {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd"}, + {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a"}, + {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959"}, + {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02"}, + {file = "coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f"}, + {file = "coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0"}, + {file = "coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5"}, + {file = "coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5"}, + {file = "coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9"}, + {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6"}, + {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e"}, + {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050"}, + {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5"}, + {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f"}, + {file = "coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e"}, + {file = "coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c"}, + {file = "coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9"}, + {file = "coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2"}, + {file = "coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7"}, + {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e"}, + {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1"}, + {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9"}, + {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250"}, + {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2"}, + {file = "coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb"}, + {file = "coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27"}, + {file = "coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d"}, + {file = "coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59"}, +] + +[[package]] +name = "coverage" +version = "7.2.7" +extras = ["toml"] +requires_python = ">=3.7" +summary = "Code coverage measurement for Python" +dependencies = [ + "coverage==7.2.7", + "tomli; python_full_version <= \"3.11.0a6\"", +] +files = [ + {file = "coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8"}, + {file = "coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb"}, + {file = "coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6"}, + {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2"}, + {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063"}, + {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1"}, + {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353"}, + {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495"}, + {file = "coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818"}, + {file = "coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850"}, + {file = "coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f"}, + {file = "coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe"}, + {file = "coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3"}, + {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f"}, + {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb"}, + {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833"}, + {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97"}, + {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a"}, + {file = "coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a"}, + {file = "coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562"}, + {file = "coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4"}, + {file = "coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4"}, + {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01"}, + {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6"}, + {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d"}, + {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de"}, + {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d"}, + {file = "coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511"}, + {file = "coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3"}, + {file = "coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f"}, + {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb"}, + {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9"}, + {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd"}, + {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a"}, + {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959"}, + {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02"}, + {file = "coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f"}, + {file = "coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0"}, + {file = "coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5"}, + {file = "coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5"}, + {file = "coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9"}, + {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6"}, + {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e"}, + {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050"}, + {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5"}, + {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f"}, + {file = "coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e"}, + {file = "coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c"}, + {file = "coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9"}, + {file = "coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2"}, + {file = "coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7"}, + {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e"}, + {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1"}, + {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9"}, + {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250"}, + {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2"}, + {file = "coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb"}, + {file = "coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27"}, + {file = "coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d"}, + {file = "coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59"}, +] + +[[package]] +name = "dill" +version = "0.3.7" +requires_python = ">=3.7" +summary = "serialize all of Python" +files = [ + {file = "dill-0.3.7-py3-none-any.whl", hash = "sha256:76b122c08ef4ce2eedcd4d1abd8e641114bfc6c2867f49f3c41facf65bf19f5e"}, + {file = "dill-0.3.7.tar.gz", hash = "sha256:cc1c8b182eb3013e24bd475ff2e9295af86c1a38eb1aff128dac8962a9ce3c03"}, +] + +[[package]] +name = "distlib" +version = "0.3.7" +summary = "Distribution utilities" +files = [ + {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, + {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.1.2" +requires_python = ">=3.7" +summary = "Backport of PEP 654 (exception groups)" +files = [ + {file = "exceptiongroup-1.1.2-py3-none-any.whl", hash = "sha256:e346e69d186172ca7cf029c8c1d16235aa0e04035e5750b4b95039e65204328f"}, + {file = "exceptiongroup-1.1.2.tar.gz", hash = "sha256:12c3e887d6485d16943a309616de20ae5582633e0a2eda17f4e10fd61c1e8af5"}, +] + +[[package]] +name = "filelock" +version = "3.12.2" +requires_python = ">=3.7" +summary = "A platform independent file lock." +files = [ + {file = "filelock-3.12.2-py3-none-any.whl", hash = "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec"}, + {file = "filelock-3.12.2.tar.gz", hash = "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81"}, +] + +[[package]] +name = "freezegun" +version = "1.2.2" +requires_python = ">=3.6" +summary = "Let your Python tests travel through time" +dependencies = [ + "python-dateutil>=2.7", +] +files = [ + {file = "freezegun-1.2.2-py3-none-any.whl", hash = "sha256:ea1b963b993cb9ea195adbd893a48d573fda951b0da64f60883d7e988b606c9f"}, + {file = "freezegun-1.2.2.tar.gz", hash = "sha256:cd22d1ba06941384410cd967d8a99d5ae2442f57dfafeff2fda5de8dc5c05446"}, +] + +[[package]] +name = "identify" +version = "2.5.26" +requires_python = ">=3.8" +summary = "File identification library for Python" +files = [ + {file = "identify-2.5.26-py2.py3-none-any.whl", hash = "sha256:c22a8ead0d4ca11f1edd6c9418c3220669b3b7533ada0a0ffa6cc0ef85cf9b54"}, + {file = "identify-2.5.26.tar.gz", hash = "sha256:7243800bce2f58404ed41b7c002e53d4d22bcf3ae1b7900c2d7aefd95394bf7f"}, +] + +[[package]] +name = "idna" +version = "3.4" +requires_python = ">=3.5" +summary = "Internationalized Domain Names in Applications (IDNA)" +files = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] + +[[package]] +name = "importlib-metadata" +version = "6.8.0" +requires_python = ">=3.8" +summary = "Read metadata from Python packages" +dependencies = [ + "zipp>=0.5", +] +files = [ + {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, + {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +requires_python = ">=3.7" +summary = "brain-dead simple config-ini parsing" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "isort" +version = "5.12.0" +requires_python = ">=3.8.0" +summary = "A Python utility / library to sort Python imports." +files = [ + {file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"}, + {file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"}, +] + +[[package]] +name = "kombu" +version = "5.3.1" +requires_python = ">=3.8" +summary = "Messaging library for Python." +dependencies = [ + "amqp<6.0.0,>=5.1.1", + "backports-zoneinfo[tzdata]>=0.2.1; python_version < \"3.9\"", + "typing-extensions; python_version < \"3.10\"", + "vine", +] +files = [ + {file = "kombu-5.3.1-py3-none-any.whl", hash = "sha256:48ee589e8833126fd01ceaa08f8a2041334e9f5894e5763c8486a550454551e9"}, + {file = "kombu-5.3.1.tar.gz", hash = "sha256:fbd7572d92c0bf71c112a6b45163153dea5a7b6a701ec16b568c27d0fd2370f2"}, +] + +[[package]] +name = "lazy-object-proxy" +version = "1.9.0" +requires_python = ">=3.7" +summary = "A fast and thorough lazy object proxy." +files = [ + {file = "lazy-object-proxy-1.9.0.tar.gz", hash = "sha256:659fb5809fa4629b8a1ac5106f669cfc7bef26fbb389dda53b3e010d1ac4ebae"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b40387277b0ed2d0602b8293b94d7257e17d1479e257b4de114ea11a8cb7f2d7"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8c6cfb338b133fbdbc5cfaa10fe3c6aeea827db80c978dbd13bc9dd8526b7d4"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:721532711daa7db0d8b779b0bb0318fa87af1c10d7fe5e52ef30f8eff254d0cd"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66a3de4a3ec06cd8af3f61b8e1ec67614fbb7c995d02fa224813cb7afefee701"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1aa3de4088c89a1b69f8ec0dcc169aa725b0ff017899ac568fe44ddc1396df46"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-win32.whl", hash = "sha256:f0705c376533ed2a9e5e97aacdbfe04cecd71e0aa84c7c0595d02ef93b6e4455"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:ea806fd4c37bf7e7ad82537b0757999264d5f70c45468447bb2b91afdbe73a6e"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:946d27deaff6cf8452ed0dba83ba38839a87f4f7a9732e8f9fd4107b21e6ff07"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79a31b086e7e68b24b99b23d57723ef7e2c6d81ed21007b6281ebcd1688acb0a"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f699ac1c768270c9e384e4cbd268d6e67aebcfae6cd623b4d7c3bfde5a35db59"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfb38f9ffb53b942f2b5954e0f610f1e721ccebe9cce9025a38c8ccf4a5183a4"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:189bbd5d41ae7a498397287c408617fe5c48633e7755287b21d741f7db2706a9"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-win32.whl", hash = "sha256:81fc4d08b062b535d95c9ea70dbe8a335c45c04029878e62d744bdced5141586"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:f2457189d8257dd41ae9b434ba33298aec198e30adf2dcdaaa3a28b9994f6adb"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9e25ef10a39e8afe59a5c348a4dbf29b4868ab76269f81ce1674494e2565a6e"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cbf9b082426036e19c6924a9ce90c740a9861e2bdc27a4834fd0a910742ac1e8"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f5fa4a61ce2438267163891961cfd5e32ec97a2c444e5b842d574251ade27d2"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8fa02eaab317b1e9e03f69aab1f91e120e7899b392c4fc19807a8278a07a97e8"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e7c21c95cae3c05c14aafffe2865bbd5e377cfc1348c4f7751d9dc9a48ca4bda"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win32.whl", hash = "sha256:f12ad7126ae0c98d601a7ee504c1122bcef553d1d5e0c3bfa77b16b3968d2734"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:edd20c5a55acb67c7ed471fa2b5fb66cb17f61430b7a6b9c3b4a1e40293b1671"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0daa332786cf3bb49e10dc6a17a52f6a8f9601b4cf5c295a4f85854d61de63"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cd077f3d04a58e83d04b20e334f678c2b0ff9879b9375ed107d5d07ff160171"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:660c94ea760b3ce47d1855a30984c78327500493d396eac4dfd8bd82041b22be"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:212774e4dfa851e74d393a2370871e174d7ff0ebc980907723bb67d25c8a7c30"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0117049dd1d5635bbff65444496c90e0baa48ea405125c088e93d9cf4525b11"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-win32.whl", hash = "sha256:0a891e4e41b54fd5b8313b96399f8b0e173bbbfc03c7631f01efbe29bb0bcf82"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:9990d8e71b9f6488e91ad25f322898c136b008d87bf852ff65391b004da5e17b"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9e7551208b2aded9c1447453ee366f1c4070602b3d932ace044715d89666899b"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f83ac4d83ef0ab017683d715ed356e30dd48a93746309c8f3517e1287523ef4"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7322c3d6f1766d4ef1e51a465f47955f1e8123caee67dd641e67d539a534d006"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:18b78ec83edbbeb69efdc0e9c1cb41a3b1b1ed11ddd8ded602464c3fc6020494"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:09763491ce220c0299688940f8dc2c5d05fd1f45af1e42e636b2e8b2303e4382"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-win32.whl", hash = "sha256:9090d8e53235aa280fc9239a86ae3ea8ac58eff66a705fa6aa2ec4968b95c821"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:db1c1722726f47e10e0b5fdbf15ac3b8adb58c091d12b3ab713965795036985f"}, +] + +[[package]] +name = "mccabe" +version = "0.7.0" +requires_python = ">=3.6" +summary = "McCabe checker, plugin for flake8" +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] + +[[package]] +name = "nodeenv" +version = "1.8.0" +requires_python = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +summary = "Node.js virtual environment builder" +dependencies = [ + "setuptools", +] +files = [ + {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, + {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, +] + +[[package]] +name = "packaging" +version = "23.1" +requires_python = ">=3.7" +summary = "Core utilities for Python packages" +files = [ + {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, + {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, +] + +[[package]] +name = "pip" +version = "23.2.1" +requires_python = ">=3.7" +summary = "The PyPA recommended tool for installing Python packages." +files = [ + {file = "pip-23.2.1-py3-none-any.whl", hash = "sha256:7ccf472345f20d35bdc9d1841ff5f313260c2c33fe417f48c30ac46cccabf5be"}, + {file = "pip-23.2.1.tar.gz", hash = "sha256:fb0bd5435b3200c602b5bf61d2d43c2f13c02e29c1707567ae7fbc514eb9faf2"}, +] + +[[package]] +name = "pip-tools" +version = "7.1.0" +requires_python = ">=3.8" +summary = "pip-tools keeps your pinned dependencies fresh." +dependencies = [ + "build", + "click>=8", + "pip>=22.2", + "setuptools", + "tomli; python_version < \"3.11\"", + "wheel", +] +files = [ + {file = "pip-tools-7.1.0.tar.gz", hash = "sha256:f6ead499e726c8cfee04b2dea6282a9faf29663c378d9a4aca2ea6b86c8ec715"}, + {file = "pip_tools-7.1.0-py3-none-any.whl", hash = "sha256:4e60b7d05b046f49ad5bf3c2818df8e78dec5820e9b331cd9898cff5ec19ff2f"}, +] + +[[package]] +name = "platformdirs" +version = "3.9.1" +requires_python = ">=3.7" +summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +files = [ + {file = "platformdirs-3.9.1-py3-none-any.whl", hash = "sha256:ad8291ae0ae5072f66c16945166cb11c63394c7a3ad1b1bc9828ca3162da8c2f"}, + {file = "platformdirs-3.9.1.tar.gz", hash = "sha256:1b42b450ad933e981d56e59f1b97495428c9bd60698baab9f3eb3d00d5822421"}, +] + +[[package]] +name = "pluggy" +version = "1.2.0" +requires_python = ">=3.7" +summary = "plugin and hook calling mechanisms for python" +files = [ + {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, + {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, +] + +[[package]] +name = "pre-commit" +version = "3.3.3" +requires_python = ">=3.8" +summary = "A framework for managing and maintaining multi-language pre-commit hooks." +dependencies = [ + "cfgv>=2.0.0", + "identify>=1.0.0", + "nodeenv>=0.11.1", + "pyyaml>=5.1", + "virtualenv>=20.10.0", +] +files = [ + {file = "pre_commit-3.3.3-py2.py3-none-any.whl", hash = "sha256:10badb65d6a38caff29703362271d7dca483d01da88f9d7e05d0b97171c136cb"}, + {file = "pre_commit-3.3.3.tar.gz", hash = "sha256:a2256f489cd913d575c145132ae196fe335da32d91a8294b7afe6622335dd023"}, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.39" +requires_python = ">=3.7.0" +summary = "Library for building powerful interactive command lines in Python" +dependencies = [ + "wcwidth", +] +files = [ + {file = "prompt_toolkit-3.0.39-py3-none-any.whl", hash = "sha256:9dffbe1d8acf91e3de75f3b544e4842382fc06c6babe903ac9acb74dc6e08d88"}, + {file = "prompt_toolkit-3.0.39.tar.gz", hash = "sha256:04505ade687dc26dc4284b1ad19a83be2f2afe83e7a828ace0c72f3a1df72aac"}, +] + +[[package]] +name = "pydantic" +version = "1.10.11" +requires_python = ">=3.7" +summary = "Data validation and settings management using python type hints" +dependencies = [ + "typing-extensions>=4.2.0", +] +files = [ + {file = "pydantic-1.10.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ff44c5e89315b15ff1f7fdaf9853770b810936d6b01a7bcecaa227d2f8fe444f"}, + {file = "pydantic-1.10.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a6c098d4ab5e2d5b3984d3cb2527e2d6099d3de85630c8934efcfdc348a9760e"}, + {file = "pydantic-1.10.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16928fdc9cb273c6af00d9d5045434c39afba5f42325fb990add2c241402d151"}, + {file = "pydantic-1.10.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0588788a9a85f3e5e9ebca14211a496409cb3deca5b6971ff37c556d581854e7"}, + {file = "pydantic-1.10.11-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e9baf78b31da2dc3d3f346ef18e58ec5f12f5aaa17ac517e2ffd026a92a87588"}, + {file = "pydantic-1.10.11-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:373c0840f5c2b5b1ccadd9286782852b901055998136287828731868027a724f"}, + {file = "pydantic-1.10.11-cp310-cp310-win_amd64.whl", hash = "sha256:c3339a46bbe6013ef7bdd2844679bfe500347ac5742cd4019a88312aa58a9847"}, + {file = "pydantic-1.10.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:08a6c32e1c3809fbc49debb96bf833164f3438b3696abf0fbeceb417d123e6eb"}, + {file = "pydantic-1.10.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a451ccab49971af043ec4e0d207cbc8cbe53dbf148ef9f19599024076fe9c25b"}, + {file = "pydantic-1.10.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b02d24f7b2b365fed586ed73582c20f353a4c50e4be9ba2c57ab96f8091ddae"}, + {file = "pydantic-1.10.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f34739a89260dfa420aa3cbd069fbcc794b25bbe5c0a214f8fb29e363484b66"}, + {file = "pydantic-1.10.11-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e297897eb4bebde985f72a46a7552a7556a3dd11e7f76acda0c1093e3dbcf216"}, + {file = "pydantic-1.10.11-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d185819a7a059550ecb85d5134e7d40f2565f3dd94cfd870132c5f91a89cf58c"}, + {file = "pydantic-1.10.11-cp311-cp311-win_amd64.whl", hash = "sha256:4400015f15c9b464c9db2d5d951b6a780102cfa5870f2c036d37c23b56f7fc1b"}, + {file = "pydantic-1.10.11-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2417de68290434461a266271fc57274a138510dca19982336639484c73a07af6"}, + {file = "pydantic-1.10.11-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:331c031ba1554b974c98679bd0780d89670d6fd6f53f5d70b10bdc9addee1713"}, + {file = "pydantic-1.10.11-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8268a735a14c308923e8958363e3a3404f6834bb98c11f5ab43251a4e410170c"}, + {file = "pydantic-1.10.11-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:44e51ba599c3ef227e168424e220cd3e544288c57829520dc90ea9cb190c3248"}, + {file = "pydantic-1.10.11-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d7781f1d13b19700b7949c5a639c764a077cbbdd4322ed505b449d3ca8edcb36"}, + {file = "pydantic-1.10.11-cp37-cp37m-win_amd64.whl", hash = "sha256:7522a7666157aa22b812ce14c827574ddccc94f361237ca6ea8bb0d5c38f1629"}, + {file = "pydantic-1.10.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc64eab9b19cd794a380179ac0e6752335e9555d214cfcb755820333c0784cb3"}, + {file = "pydantic-1.10.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8dc77064471780262b6a68fe67e013298d130414d5aaf9b562c33987dbd2cf4f"}, + {file = "pydantic-1.10.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe429898f2c9dd209bd0632a606bddc06f8bce081bbd03d1c775a45886e2c1cb"}, + {file = "pydantic-1.10.11-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:192c608ad002a748e4a0bed2ddbcd98f9b56df50a7c24d9a931a8c5dd053bd3d"}, + {file = "pydantic-1.10.11-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ef55392ec4bb5721f4ded1096241e4b7151ba6d50a50a80a2526c854f42e6a2f"}, + {file = "pydantic-1.10.11-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:41e0bb6efe86281623abbeeb0be64eab740c865388ee934cd3e6a358784aca6e"}, + {file = "pydantic-1.10.11-cp38-cp38-win_amd64.whl", hash = "sha256:265a60da42f9f27e0b1014eab8acd3e53bd0bad5c5b4884e98a55f8f596b2c19"}, + {file = "pydantic-1.10.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:469adf96c8e2c2bbfa655fc7735a2a82f4c543d9fee97bd113a7fb509bf5e622"}, + {file = "pydantic-1.10.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e6cbfbd010b14c8a905a7b10f9fe090068d1744d46f9e0c021db28daeb8b6de1"}, + {file = "pydantic-1.10.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abade85268cc92dff86d6effcd917893130f0ff516f3d637f50dadc22ae93999"}, + {file = "pydantic-1.10.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e9738b0f2e6c70f44ee0de53f2089d6002b10c33264abee07bdb5c7f03038303"}, + {file = "pydantic-1.10.11-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:787cf23e5a0cde753f2eabac1b2e73ae3844eb873fd1f5bdbff3048d8dbb7604"}, + {file = "pydantic-1.10.11-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:174899023337b9fc685ac8adaa7b047050616136ccd30e9070627c1aaab53a13"}, + {file = "pydantic-1.10.11-cp39-cp39-win_amd64.whl", hash = "sha256:1954f8778489a04b245a1e7b8b22a9d3ea8ef49337285693cf6959e4b757535e"}, + {file = "pydantic-1.10.11-py3-none-any.whl", hash = "sha256:008c5e266c8aada206d0627a011504e14268a62091450210eda7c07fabe6963e"}, + {file = "pydantic-1.10.11.tar.gz", hash = "sha256:f66d479cf7eb331372c470614be6511eae96f1f120344c25f3f9bb59fb1b5528"}, +] + +[[package]] +name = "pyfakefs" +version = "5.2.3" +requires_python = ">=3.7" +summary = "pyfakefs implements a fake file system that mocks the Python file system modules." +files = [ + {file = "pyfakefs-5.2.3-py3-none-any.whl", hash = "sha256:101a91d8e454934fe2435392c38d505beacfc27dad71a0ad2a6215d0b750f2f1"}, + {file = "pyfakefs-5.2.3.tar.gz", hash = "sha256:f4d677645e44c56fd47d579c7586ff0daef1546d3100df2af50969f794368fc6"}, +] + +[[package]] +name = "pylint" +version = "2.17.4" +requires_python = ">=3.7.2" +summary = "python code static checker" +dependencies = [ + "astroid<=2.17.0-dev0,>=2.15.4", + "colorama>=0.4.5; sys_platform == \"win32\"", + "dill>=0.2; python_version < \"3.11\"", + "dill>=0.3.6; python_version >= \"3.11\"", + "isort<6,>=4.2.5", + "mccabe<0.8,>=0.6", + "platformdirs>=2.2.0", + "tomli>=1.1.0; python_version < \"3.11\"", + "tomlkit>=0.10.1", + "typing-extensions>=3.10.0; python_version < \"3.10\"", +] +files = [ + {file = "pylint-2.17.4-py3-none-any.whl", hash = "sha256:7a1145fb08c251bdb5cca11739722ce64a63db479283d10ce718b2460e54123c"}, + {file = "pylint-2.17.4.tar.gz", hash = "sha256:5dcf1d9e19f41f38e4e85d10f511e5b9c35e1aa74251bf95cdd8cb23584e2db1"}, +] + +[[package]] +name = "pyproject-hooks" +version = "1.0.0" +requires_python = ">=3.7" +summary = "Wrappers to call pyproject.toml-based build backend hooks." +dependencies = [ + "tomli>=1.1.0; python_version < \"3.11\"", +] +files = [ + {file = "pyproject_hooks-1.0.0-py3-none-any.whl", hash = "sha256:283c11acd6b928d2f6a7c73fa0d01cb2bdc5f07c57a2eeb6e83d5e56b97976f8"}, + {file = "pyproject_hooks-1.0.0.tar.gz", hash = "sha256:f271b298b97f5955d53fb12b72c1fb1948c22c1a6b70b315c54cedaca0264ef5"}, +] + +[[package]] +name = "pytest" +version = "7.4.0" +requires_python = ">=3.7" +summary = "pytest: simple powerful testing with Python" +dependencies = [ + "colorama; sys_platform == \"win32\"", + "exceptiongroup>=1.0.0rc8; python_version < \"3.11\"", + "iniconfig", + "packaging", + "pluggy<2.0,>=0.12", + "tomli>=1.0.0; python_version < \"3.11\"", +] +files = [ + {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, + {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, +] + +[[package]] +name = "pytest-asyncio" +version = "0.15.1" +requires_python = ">= 3.6" +summary = "Pytest support for asyncio." +dependencies = [ + "pytest>=5.4.0", +] +files = [ + {file = "pytest-asyncio-0.15.1.tar.gz", hash = "sha256:2564ceb9612bbd560d19ca4b41347b54e7835c2f792c504f698e05395ed63f6f"}, + {file = "pytest_asyncio-0.15.1-py3-none-any.whl", hash = "sha256:3042bcdf1c5d978f6b74d96a151c4cfb9dcece65006198389ccd7e6c60eb1eea"}, +] + +[[package]] +name = "pytest-celery" +version = "0.0.0" +summary = "pytest-celery a shim pytest plugin to enable celery.contrib.pytest" +dependencies = [ + "celery>=4.4.0", +] +files = [ + {file = "pytest-celery-0.0.0.tar.gz", hash = "sha256:cfd060fc32676afa1e4f51b2938f903f7f75d952186b8c6cf631628c4088f406"}, + {file = "pytest_celery-0.0.0-py2.py3-none-any.whl", hash = "sha256:63dec132df3a839226ecb003ffdbb0c2cb88dd328550957e979c942766578060"}, +] + +[[package]] +name = "pytest-cov" +version = "4.1.0" +requires_python = ">=3.7" +summary = "Pytest plugin for measuring coverage." +dependencies = [ + "coverage[toml]>=5.2.1", + "pytest>=4.6", +] +files = [ + {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, + {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, +] + +[[package]] +name = "pytest-freezegun" +version = "0.4.2" +summary = "Wrap tests with fixtures in freeze_time" +dependencies = [ + "freezegun>0.3", + "pytest>=3.0.0", +] +files = [ + {file = "pytest-freezegun-0.4.2.zip", hash = "sha256:19c82d5633751bf3ec92caa481fb5cffaac1787bd485f0df6436fd6242176949"}, + {file = "pytest_freezegun-0.4.2-py2.py3-none-any.whl", hash = "sha256:5318a6bfb8ba4b709c8471c94d0033113877b3ee02da5bfcd917c1889cde99a7"}, +] + +[[package]] +name = "pytest-integration" +version = "0.2.2" +requires_python = ">=3.6" +summary = "Organizing pytests by integration or not" +files = [ + {file = "pytest_integration-0.2.2-py3-none-any.whl", hash = "sha256:560b18c003cf6a3d6672878e826a823ea5f8d1d289dbe97546495040b2f0bd3d"}, + {file = "pytest_integration-0.2.2.tar.gz", hash = "sha256:7630b2bb1a8d518168bae44d827c20c4f0c1bbc5a1d3e1014dc5624ccadcdbd1"}, +] + +[[package]] +name = "pytest-mock" +version = "3.11.1" +requires_python = ">=3.7" +summary = "Thin-wrapper around the mock package for easier use with pytest" +dependencies = [ + "pytest>=5.0", +] +files = [ + {file = "pytest-mock-3.11.1.tar.gz", hash = "sha256:7f6b125602ac6d743e523ae0bfa71e1a697a2f5534064528c6ff84c2f7c2fc7f"}, + {file = "pytest_mock-3.11.1-py3-none-any.whl", hash = "sha256:21c279fff83d70763b05f8874cc9cfb3fcacd6d354247a976f9529d19f9acf39"}, +] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +summary = "Extensions to the standard Python datetime module" +dependencies = [ + "six>=1.5", +] +files = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] + +[[package]] +name = "python-dotenv" +version = "0.19.2" +requires_python = ">=3.5" +summary = "Read key-value pairs from a .env file and set them as environment variables" +files = [ + {file = "python-dotenv-0.19.2.tar.gz", hash = "sha256:a5de49a31e953b45ff2d2fd434bbc2670e8db5273606c1e737cc6b93eff3655f"}, + {file = "python_dotenv-0.19.2-py2.py3-none-any.whl", hash = "sha256:32b2bdc1873fd3a3c346da1c6db83d0053c3c62f28f1f38516070c4c8971b1d3"}, +] + +[[package]] +name = "pyyaml" +version = "6.0.1" +requires_python = ">=3.6" +summary = "YAML parser and emitter for Python" +files = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] + +[[package]] +name = "redis" +version = "4.6.0" +requires_python = ">=3.7" +summary = "Python client for Redis database and key-value store" +dependencies = [ + "async-timeout>=4.0.2; python_full_version <= \"3.11.2\"", +] +files = [ + {file = "redis-4.6.0-py3-none-any.whl", hash = "sha256:e2b03db868160ee4591de3cb90d40ebb50a90dd302138775937f6a42b7ed183c"}, + {file = "redis-4.6.0.tar.gz", hash = "sha256:585dc516b9eb042a619ef0a39c3d7d55fe81bdb4df09a52c9cdde0d07bf1aa7d"}, +] + +[[package]] +name = "requests" +version = "2.31.0" +requires_python = ">=3.7" +summary = "Python HTTP for Humans." +dependencies = [ + "certifi>=2017.4.17", + "charset-normalizer<4,>=2", + "idna<4,>=2.5", + "urllib3<3,>=1.21.1", +] +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[[package]] +name = "requests-mock" +version = "1.11.0" +summary = "Mock out responses from the requests package" +dependencies = [ + "requests<3,>=2.3", + "six", +] +files = [ + {file = "requests-mock-1.11.0.tar.gz", hash = "sha256:ef10b572b489a5f28e09b708697208c4a3b2b89ef80a9f01584340ea357ec3c4"}, + {file = "requests_mock-1.11.0-py2.py3-none-any.whl", hash = "sha256:f7fae383f228633f6bececebdab236c478ace2284d6292c6e7e2867b9ab74d15"}, +] + +[[package]] +name = "setuptools" +version = "68.0.0" +requires_python = ">=3.7" +summary = "Easily download, build, install, upgrade, and uninstall Python packages" +files = [ + {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, + {file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, +] + +[[package]] +name = "six" +version = "1.16.0" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +summary = "Python 2 and 3 compatibility utilities" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "tomli" +version = "2.0.1" +requires_python = ">=3.7" +summary = "A lil' TOML parser" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "tomlkit" +version = "0.11.8" +requires_python = ">=3.7" +summary = "Style preserving TOML library" +files = [ + {file = "tomlkit-0.11.8-py3-none-any.whl", hash = "sha256:8c726c4c202bdb148667835f68d68780b9a003a9ec34167b6c673b38eff2a171"}, + {file = "tomlkit-0.11.8.tar.gz", hash = "sha256:9330fc7faa1db67b541b28e62018c17d20be733177d290a13b24c62d1614e0c3"}, +] + +[[package]] +name = "typing-extensions" +version = "4.7.1" +requires_python = ">=3.7" +summary = "Backported and Experimental Type Hints for Python 3.7+" +files = [ + {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, + {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, +] + +[[package]] +name = "tzdata" +version = "2023.3" +requires_python = ">=2" +summary = "Provider of IANA time zone data" +files = [ + {file = "tzdata-2023.3-py2.py3-none-any.whl", hash = "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"}, + {file = "tzdata-2023.3.tar.gz", hash = "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a"}, +] + +[[package]] +name = "urllib3" +version = "2.0.4" +requires_python = ">=3.7" +summary = "HTTP library with thread-safe connection pooling, file post, and more." +files = [ + {file = "urllib3-2.0.4-py3-none-any.whl", hash = "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4"}, + {file = "urllib3-2.0.4.tar.gz", hash = "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11"}, +] + +[[package]] +name = "vine" +version = "5.0.0" +requires_python = ">=3.6" +summary = "Promises, promises, promises." +files = [ + {file = "vine-5.0.0-py2.py3-none-any.whl", hash = "sha256:4c9dceab6f76ed92105027c49c823800dd33cacce13bdedc5b914e3514b7fb30"}, + {file = "vine-5.0.0.tar.gz", hash = "sha256:7d3b1624a953da82ef63462013bbd271d3eb75751489f9807598e8f340bd637e"}, +] + +[[package]] +name = "virtualenv" +version = "20.24.1" +requires_python = ">=3.7" +summary = "Virtual Python Environment builder" +dependencies = [ + "distlib<1,>=0.3.6", + "filelock<4,>=3.12", + "platformdirs<4,>=3.5.1", +] +files = [ + {file = "virtualenv-20.24.1-py3-none-any.whl", hash = "sha256:01aacf8decd346cf9a865ae85c0cdc7f64c8caa07ff0d8b1dfc1733d10677442"}, + {file = "virtualenv-20.24.1.tar.gz", hash = "sha256:2ef6a237c31629da6442b0bcaa3999748108c7166318d1f55cc9f8d7294e97bd"}, +] + +[[package]] +name = "wcwidth" +version = "0.2.6" +summary = "Measures the displayed width of unicode strings in a terminal" +files = [ + {file = "wcwidth-0.2.6-py2.py3-none-any.whl", hash = "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e"}, + {file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"}, +] + +[[package]] +name = "wheel" +version = "0.41.0" +requires_python = ">=3.7" +summary = "A built-package format for Python" +files = [ + {file = "wheel-0.41.0-py3-none-any.whl", hash = "sha256:7e9be3bbd0078f6147d82ed9ed957e323e7708f57e134743d2edef3a7b7972a9"}, + {file = "wheel-0.41.0.tar.gz", hash = "sha256:55a0f0a5a84869bce5ba775abfd9c462e3a6b1b7b7ec69d72c0b83d673a5114d"}, +] + +[[package]] +name = "wrapt" +version = "1.15.0" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +summary = "Module for decorators, wrappers and monkey patching." +files = [ + {file = "wrapt-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a"}, + {file = "wrapt-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923"}, + {file = "wrapt-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee"}, + {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727"}, + {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7"}, + {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0"}, + {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec"}, + {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90"}, + {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975"}, + {file = "wrapt-1.15.0-cp310-cp310-win32.whl", hash = "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1"}, + {file = "wrapt-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e"}, + {file = "wrapt-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7"}, + {file = "wrapt-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72"}, + {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb"}, + {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e"}, + {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c"}, + {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3"}, + {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92"}, + {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98"}, + {file = "wrapt-1.15.0-cp311-cp311-win32.whl", hash = "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416"}, + {file = "wrapt-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248"}, + {file = "wrapt-1.15.0-cp35-cp35m-win32.whl", hash = "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559"}, + {file = "wrapt-1.15.0-cp35-cp35m-win_amd64.whl", hash = "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"}, + {file = "wrapt-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba"}, + {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752"}, + {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364"}, + {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475"}, + {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8"}, + {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418"}, + {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2"}, + {file = "wrapt-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1"}, + {file = "wrapt-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420"}, + {file = "wrapt-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317"}, + {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e"}, + {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e"}, + {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0"}, + {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019"}, + {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034"}, + {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653"}, + {file = "wrapt-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0"}, + {file = "wrapt-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e"}, + {file = "wrapt-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145"}, + {file = "wrapt-1.15.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f"}, + {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd"}, + {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b"}, + {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f"}, + {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6"}, + {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094"}, + {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7"}, + {file = "wrapt-1.15.0-cp38-cp38-win32.whl", hash = "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b"}, + {file = "wrapt-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1"}, + {file = "wrapt-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86"}, + {file = "wrapt-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c"}, + {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d"}, + {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc"}, + {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29"}, + {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a"}, + {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8"}, + {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9"}, + {file = "wrapt-1.15.0-cp39-cp39-win32.whl", hash = "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff"}, + {file = "wrapt-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6"}, + {file = "wrapt-1.15.0-py3-none-any.whl", hash = "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640"}, + {file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"}, +] + +[[package]] +name = "zipp" +version = "3.16.2" +requires_python = ">=3.8" +summary = "Backport of pathlib-compatible object wrapper for zip files" +files = [ + {file = "zipp-3.16.2-py3-none-any.whl", hash = "sha256:679e51dd4403591b2d6838a48de3d283f3d188412a9782faadf845f298736ba0"}, + {file = "zipp-3.16.2.tar.gz", hash = "sha256:ebc15946aa78bd63458992fc81ec3b6f7b1e92d51c35e6de1c3804e73b799147"}, +] diff --git a/datajunction-reflection/pyproject.toml b/datajunction-reflection/pyproject.toml new file mode 100644 index 000000000..d532e9ba9 --- /dev/null +++ b/datajunction-reflection/pyproject.toml @@ -0,0 +1,74 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.pdm.build] +includes = ["datajunction_reflection"] + +[project] +name = "datajunction-reflection" +dynamic = ["version"] +description = "OSS Implementation of a DataJunction Reflection Service" +authors = [ + {name = "DataJunction Authors", email = "roberto@dealmeida.net"}, +] +dependencies = [ + "importlib-metadata", + "celery[redis]>=5.2.3", + "python-dotenv==0.19.2", + "requests>=2.26.0", + "pydantic<2.0", +] +requires-python = ">=3.8,<4.0" +readme = "README.md" +license = {text = "MIT"} +classifiers = [ + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", +] + +[project.optional-dependencies] +uvicorn = [ + "uvicorn[standard]>=0.21.1", +] + +[tool.hatch.version] +path = "datajunction_reflection/__about__.py" + +[project.urls] +repository = "https://github.com/DataJunction/dj" + +[tool.pdm.dev-dependencies] +test = [ + "celery[pytest]", + "codespell>=2.1.0", + "freezegun>=1.1.0", + "pre-commit>=2.15.0", + "pyfakefs>=4.5.1", + "pylint>=2.15.3", + "pytest-asyncio==0.15.1", + "pytest-cov>=2.12.1", + "pytest-freezegun", + "pytest-integration==0.2.2", + "pytest-mock>=3.6.1", + "pytest>=6.2.5", + "requests-mock>=1.9.3", + "setuptools>=49.6.0", + "pip-tools>=6.4.0", + "typing-extensions>=4.3.0", +] + +[tool.hatch.metadata] +allow-direct-references = true + +[tool.coverage.run] +source = ['datajunction_reflection/'] + +[tool.isort] +src_paths = ["datajunction_reflection/", "tests/"] +profile = 'black' diff --git a/datajunction-reflection/setup.cfg b/datajunction-reflection/setup.cfg new file mode 100644 index 000000000..cacabf684 --- /dev/null +++ b/datajunction-reflection/setup.cfg @@ -0,0 +1,19 @@ +# This file is used to configure your project. +# Read more about the various options under: +# https://setuptools.pypa.io/en/latest/userguide/declarative_config.html +# https://setuptools.pypa.io/en/latest/references/keywords.html + +[tool:pytest] +# Specify command line options as you would do when invoking pytest directly. +# e.g. --cov-report html (or xml) for html/xml output or --junitxml junit.xml +# in order to write a coverage file that can be read by Jenkins. +# CAUTION: --cov flags may prohibit setting breakpoints while debugging. +# Comment those flags to avoid this pytest issue. +addopts = + --cov datajunction_reflection --cov-report term-missing + --verbose +norecursedirs = + dist + build + .tox +testpaths = tests diff --git a/datajunction-reflection/tests/__init__.py b/datajunction-reflection/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/datajunction-reflection/tests/conftest.py b/datajunction-reflection/tests/conftest.py new file mode 100644 index 000000000..f06791552 --- /dev/null +++ b/datajunction-reflection/tests/conftest.py @@ -0,0 +1,31 @@ +"""Test configuration.""" +import threading + +import pytest + +from datajunction_reflection.worker.app import celery_app as celeryapp + +# pytest_plugins = ("celery.contrib.pytest", ) + + +@pytest.fixture() +def celery_app(): + """ + Configure celery app for unit tests. This uses an in-memory broker + and starts a single worker. + """ + celeryapp.conf.update(CELERY_ALWAYS_EAGER=True) + celeryapp.conf.broker_url = "memory://localhost/" + celeryapp.conf.result_backend = "" + celeryapp.conf.CELERYD_CONCURRENCY = 1 + celeryapp.conf.CELERYD_POOL = "solo" + celeryapp.all_tasks = [] + + def run_worker(): + """Celery worker.""" + celeryapp.worker_main() + + thread = threading.Thread(target=run_worker) + thread.daemon = True + thread.start() + return celeryapp diff --git a/datajunction-reflection/tests/test_tasks.py b/datajunction-reflection/tests/test_tasks.py new file mode 100644 index 000000000..a5c55c0a8 --- /dev/null +++ b/datajunction-reflection/tests/test_tasks.py @@ -0,0 +1,41 @@ +"""Tests the celery app.""" +from unittest.mock import call + +from datajunction_reflection.worker.tasks import reflect_source, refresh + + +def test_refresh(celery_app, mocker): + """ + Tests that the reflection service refreshes DJ source nodes + """ + mock_dj_get_sources = mocker.patch("requests.get") + mock_dj_get_sources.return_value.json = lambda: ["postgres.test.revenue"] + mock_dj_get_sources.return_value.status_code = 200 + + refresh() + + assert { + "datajunction_reflection.worker.app.refresh", + "datajunction_reflection.worker.tasks.reflect_source", + }.intersection( + celery_app.tasks.keys(), + ) + assert mock_dj_get_sources.call_args_list == [ + call("http://dj:8000/nodes/?node_type=source", timeout=30), + ] + + +def test_reflect_source(celery_app, mocker, freezer): # pylint: disable=unused-argument + """ + Tests the reflection task. + """ + mock_dj_refresh = mocker.patch("requests.post") + mock_dj_refresh.return_value.status = 201 + + reflect_source.apply( + args=("postgres.test.revenue",), + ).get() + + assert mock_dj_refresh.call_args_list == [ + call("http://dj:8000/nodes/postgres.test.revenue/refresh/", timeout=30), + ] diff --git a/datajunction-reflection/tox.ini b/datajunction-reflection/tox.ini new file mode 100644 index 000000000..c10db6830 --- /dev/null +++ b/datajunction-reflection/tox.ini @@ -0,0 +1,14 @@ +[tox] +envlist = py3 + +[testenv] +pip_pre = true +deps = + -rrequirements/test.txt + pytest + testfixtures + coverage +commands = + pip install -e .[testing] + coverage run --source datajunction_reflection --parallel-mode -m pytest {posargs} --without-integration --without-slow-integration + coverage html --fail-under 100 -d test-reports/{envname}/coverage-html diff --git a/datajunction-server/.coveragerc b/datajunction-server/.coveragerc new file mode 100644 index 000000000..7c9a00697 --- /dev/null +++ b/datajunction-server/.coveragerc @@ -0,0 +1,33 @@ +# .coveragerc to control coverage.py +[run] +branch = True +source = datajunction_server +omit = + */datajunction_server/sql/parsing/backends/grammar/generated/* + */datajunction_server/sql/parsing/backends/antlr4.py + */datajunction_server/sql/parsing/ast.py + +[paths] +source = + datajunction_server/ + */site-packages/ + +[report] +# Regexes for lines to exclude from consideration +exclude_lines = + # Have to re-enable the standard pragma + pragma: no cover + + # Don't complain about missing debug-only code: + def __repr__ + if self\.debug + + # Don't complain if tests don't hit defensive assertion code: + raise AssertionError + raise NotImplementedError + + # Don't complain if non-runnable code isn't run: + if 0: + if __name__ == .__main__.: + + if TYPE_CHECKING: diff --git a/datajunction-server/.env b/datajunction-server/.env new file mode 100644 index 000000000..b3b27a55c --- /dev/null +++ b/datajunction-server/.env @@ -0,0 +1,2 @@ +QUERY_SERVICE=http://djqs:8001 +SECRET=a-fake-secretkey diff --git a/datajunction-server/.env.integration b/datajunction-server/.env.integration new file mode 100644 index 000000000..1dd0ac039 --- /dev/null +++ b/datajunction-server/.env.integration @@ -0,0 +1 @@ +QUERY_SERVICE=http://djqs:8001 diff --git a/datajunction-server/.flake8 b/datajunction-server/.flake8 new file mode 100644 index 000000000..d9ad0b409 --- /dev/null +++ b/datajunction-server/.flake8 @@ -0,0 +1,5 @@ +[flake8] +ignore = E203, E266, E501, W503, F403, F401 +max-line-length = 79 +max-complexity = 18 +select = B,C,E,F,W,T4,B9 diff --git a/datajunction-server/.isort.cfg b/datajunction-server/.isort.cfg new file mode 100644 index 000000000..8c960bf43 --- /dev/null +++ b/datajunction-server/.isort.cfg @@ -0,0 +1,3 @@ +[settings] +profile = black +known_first_party = dj diff --git a/datajunction-server/.pre-commit-config.yaml b/datajunction-server/.pre-commit-config.yaml new file mode 100644 index 000000000..7fba62893 --- /dev/null +++ b/datajunction-server/.pre-commit-config.yaml @@ -0,0 +1,109 @@ +files: ^datajunction-server/ +exclude: (^datajunction-server/datajunction_server/sql/parsing/backends/grammar/generated|^README.md) + +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.1.0 + hooks: + - id: trailing-whitespace + - id: check-ast + exclude: ^templates/ + - id: check-json + - id: check-merge-conflict + - id: check-xml + - id: check-yaml + - id: debug-statements + exclude: ^templates/ + - id: end-of-file-fixer + exclude: openapi.json + - id: requirements-txt-fixer + exclude: ^templates/ + - id: mixed-line-ending + args: ['--fix=auto'] # replace 'auto' with 'lf' to enforce Linux/Mac line endings or 'crlf' for Windows + +## If you want to avoid flake8 errors due to unused vars or imports: +# - repo: https://github.com/myint/autoflake.git +# rev: v1.4 +# hooks: +# - id: autoflake +# args: [ +# --in-place, +# --remove-all-unused-imports, +# --remove-unused-variables, +# ] + +- repo: https://github.com/pycqa/isort + rev: 5.12.0 + hooks: + - id: isort + +- repo: https://github.com/psf/black + rev: 22.8.0 + hooks: + - id: black + language_version: python3 + exclude: ^templates/ + +## If like to embrace black styles even in the docs: +# - repo: https://github.com/asottile/blacken-docs +# rev: v1.9.1 +# hooks: +# - id: blacken-docs +# additional_dependencies: [black] + +- repo: https://github.com/PyCQA/flake8 + rev: 3.9.2 + hooks: + - id: flake8 + exclude: ^templates/ + ## You can add flake8 plugins via `additional_dependencies`: + # additional_dependencies: [flake8-bugbear] + +- repo: https://github.com/pre-commit/mirrors-mypy + rev: 'v0.931' # Use the sha / tag you want to point at + hooks: + - id: mypy + exclude: ^templates/ + additional_dependencies: + - types-requests + - types-freezegun + - types-python-dateutil + - types-pkg_resources + - types-PyYAML + - types-tabulate +- repo: https://github.com/asottile/add-trailing-comma + rev: v2.2.1 + hooks: + - id: add-trailing-comma +#- repo: https://github.com/asottile/reorder_python_imports +# rev: v2.5.0 +# hooks: +# - id: reorder-python-imports +# args: [--application-directories=.:src] +- repo: https://github.com/hadialqattan/pycln + rev: v2.1.7 # Possible releases: https://github.com/hadialqattan/pycln/tags + hooks: + - id: pycln + args: [--config=pyproject.toml] + exclude: ^templates/ +- repo: local + hooks: + - id: pylint + name: pylint + entry: pylint --disable=duplicate-code,use-implicit-booleaness-not-comparison,wrong-import-order + language: system + types: [python] + exclude: ^templates/ +- repo: https://github.com/kynan/nbstripout + rev: 0.6.1 + hooks: + - id: nbstripout +- repo: https://github.com/tomcatling/black-nb + rev: "0.7" + hooks: + - id: black-nb + files: '\.ipynb$' +- repo: https://github.com/pdm-project/pdm + rev: 2.6.1 + hooks: + - id: pdm-lock-check diff --git a/datajunction-server/.pylintrc b/datajunction-server/.pylintrc new file mode 100644 index 000000000..5b6fccaa9 --- /dev/null +++ b/datajunction-server/.pylintrc @@ -0,0 +1,6 @@ +[MESSAGES CONTROL] + +[MASTER] +# https://github.com/samuelcolvin/pydantic/issues/1961#issuecomment-759522422 +extension-pkg-whitelist=pydantic +ignore=templates,docs diff --git a/datajunction-server/Dockerfile b/datajunction-server/Dockerfile new file mode 100644 index 000000000..fdbdd35e6 --- /dev/null +++ b/datajunction-server/Dockerfile @@ -0,0 +1,12 @@ +FROM python:3.10 + +ARG RELOAD="--reload" +ENV RELOAD ${RELOAD} + +WORKDIR /code +COPY . /code +RUN pip install --no-cache-dir --upgrade -r /code/requirements/docker.txt +RUN pip install -e . + +CMD ["sh", "-c", "opentelemetry-instrument uvicorn datajunction_server.api.main:app --host 0.0.0.0 --port 8000 $RELOAD"] +EXPOSE 8000 diff --git a/datajunction-server/Makefile b/datajunction-server/Makefile new file mode 100644 index 000000000..3cbd31bd7 --- /dev/null +++ b/datajunction-server/Makefile @@ -0,0 +1,45 @@ +pyenv: .python-version + +.python-version: setup.cfg + if [ -z "`pyenv virtualenvs | grep dj`" ]; then\ + pyenv virtualenv dj;\ + fi + if [ ! -f .python-version ]; then\ + pyenv local dj;\ + fi + pip install -r requirements/test.txt + touch .python-version + +docker-build: + docker build . + docker compose build + +docker-run: + docker compose up + +test: + pdm run pytest -n auto --cov=datajunction_server --cov-report=html -vv tests/ --doctest-modules datajunction_server --without-integration --without-slow-integration ${PYTEST_ARGS} + +integration: + pdm run pytest --cov=dj -vv tests/ --doctest-modules datajunction_server --with-integration --with-slow-integration + +clean: + pyenv virtualenv-delete dj + +spellcheck: + codespell -L froms -S "*.json" dj docs/*rst tests templates + +check: + pdm run pre-commit run --all-files + +version: + @hatch version $(v) + @git add __about__.py + @git commit -m "Bumping to v$$(hatch version)" + @git tag v$$(hatch version) + @git push + @git push --tags + @hatch version + +release: + @hatch publish diff --git a/datajunction-server/README.md b/datajunction-server/README.md new file mode 100644 index 000000000..6fe7ee418 --- /dev/null +++ b/datajunction-server/README.md @@ -0,0 +1,42 @@ +# DataJunction + +## Introduction + +DataJunction (DJ) is an open source **metrics platform** that allows users to define +metrics and the data models behind them using **SQL**, serving as a **semantic layer** +on top of a physical data warehouse. By leveraging this metadata, DJ can enable efficient +retrieval of metrics data across different dimensions and filters. + +![DataJunction](docs/static/datajunction-illustration.png) + +## Getting Started + +To launch the DataJunction UI with a minimal DataJunction backend, start the default docker compose environment. + +```sh +docker compose up +``` + +If you'd like to launch the full suite of services, including open-source implementations of the DataJunction query service and +DataJunction reflection service specifications, use the `demo` profile. + +```sh +docker compose --profile demo up +``` + +DJUI: [http://localhost:3000/](http://localhost:3000/) +DJ Swagger Docs: [http://localhost:8000/docs](http://localhost:8000/docs) +DJQS Swagger Docs: [http://localhost:8001/docs](http://localhost:8001/docs) +Jaeger UI: [http://localhost:16686/search](http://localhost:16686/search) +Jupyter Lab: [http://localhost:8888](http://localhost:8888) + +## How does this work? + +At its core, DJ stores metrics and their upstream abstractions as interconnected nodes. +These nodes can represent a variety of elements, such as tables in a data warehouse +(**source nodes**), SQL transformation logic (**transform nodes**), dimensions logic, +metrics logic, and even selections of metrics, dimensions, and filters (**cube nodes**). + +By parsing each node's SQL into an AST and through dimensional links between columns, +DJ can infer a graph of dependencies between nodes, which allows it to find the +appropriate join paths between nodes to generate queries for metrics. diff --git a/datajunction-server/alembic.ini b/datajunction-server/alembic.ini new file mode 100644 index 000000000..5716e4937 --- /dev/null +++ b/datajunction-server/alembic.ini @@ -0,0 +1,104 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts +script_location = alembic + +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s +file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s + +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. +prepend_sys_path = . + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the python-dateutil library that can be +# installed by adding `alembic[tz]` to the pip requirements +# string value is passed to dateutil.tz.gettz() +# leave blank for localtime +# timezone = +timezone = UTC + +# max length of characters to apply to the +# "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; This defaults +# to alembic/versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "version_path_separator" below. +# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions + +# version path separator; As mentioned above, this is the character used to split +# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. +# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. +# Valid values for version_path_separator are: +# +# version_path_separator = : +# version_path_separator = ; +# version_path_separator = space +version_path_separator = os # Use os.pathsep. Default configuration used for new projects. + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +sqlalchemy.url = driver://user:pass@localhost/dbname + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/datajunction-server/alembic/README b/datajunction-server/alembic/README new file mode 100644 index 000000000..2500aa1bc --- /dev/null +++ b/datajunction-server/alembic/README @@ -0,0 +1 @@ +Generic single-database configuration. diff --git a/datajunction-server/alembic/env.py b/datajunction-server/alembic/env.py new file mode 100644 index 000000000..f150e1585 --- /dev/null +++ b/datajunction-server/alembic/env.py @@ -0,0 +1,89 @@ +""" +Environment for Alembic migrations. +""" +# pylint: disable=no-member, unused-import, no-name-in-module, import-error + +from logging.config import fileConfig + +from sqlmodel import SQLModel, create_engine + +from alembic import context +from datajunction_server.models import ( + Catalog, + Column, + Database, + Engine, + History, + NodeRevision, + Table, + User, +) +from datajunction_server.utils import get_settings + +settings = get_settings() + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +if config.config_file_name is not None: + fileConfig(config.config_file_name) + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +target_metadata = SQLModel.metadata + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = settings.index + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + connectable = create_engine(settings.index) + + with connectable.connect() as connection: + context.configure(connection=connection, target_metadata=target_metadata) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/datajunction-server/alembic/script.py.mako b/datajunction-server/alembic/script.py.mako new file mode 100644 index 000000000..1c2bf5315 --- /dev/null +++ b/datajunction-server/alembic/script.py.mako @@ -0,0 +1,27 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +# pylint: disable=no-member, invalid-name, missing-function-docstring, unused-import, no-name-in-module + +import sqlalchemy as sa +import sqlmodel +from alembic import op +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/datajunction-server/alembic/versions/2023_07_07_2006-4e1ff36c27c6_initial_migration.py b/datajunction-server/alembic/versions/2023_07_07_2006-4e1ff36c27c6_initial_migration.py new file mode 100644 index 000000000..adc41b042 --- /dev/null +++ b/datajunction-server/alembic/versions/2023_07_07_2006-4e1ff36c27c6_initial_migration.py @@ -0,0 +1,452 @@ +"""Initial migration + +Revision ID: 4e1ff36c27c6 +Revises: +Create Date: 2023-07-07 20:06:39.764410+00:00 + +""" +# pylint: disable=no-member, invalid-name, missing-function-docstring, unused-import, no-name-in-module + +import sqlalchemy as sa +import sqlalchemy_utils +import sqlmodel + +from alembic import op + +# revision identifiers, used by Alembic. +revision = "4e1ff36c27c6" +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + op.create_table( + "attributetype", + sa.Column("name", sa.String(), nullable=True), + sa.Column("allowed_node_types", sa.JSON(), nullable=True), + sa.Column("uniqueness_scope", sa.JSON(), nullable=True), + sa.Column("namespace", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("description", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("id", sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint("id", name=op.f("pk_attributetype")), + sa.UniqueConstraint( + "namespace", + "name", + name=op.f("uq_attributetype_namespace"), + ), + ) + op.create_table( + "availabilitystate", + sa.Column("categorical_partitions", sa.JSON(), nullable=True), + sa.Column("temporal_partitions", sa.JSON(), nullable=True), + sa.Column("min_temporal_partition", sa.JSON(), nullable=True), + sa.Column("max_temporal_partition", sa.JSON(), nullable=True), + sa.Column("partitions", sa.JSON(), nullable=True), + sa.Column("updated_at", sa.DateTime(timezone=True), nullable=True), + sa.Column("catalog", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("schema_", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column("table", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("valid_through_ts", sa.Integer(), nullable=False), + sa.Column("id", sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint("id", name=op.f("pk_availabilitystate")), + ) + op.create_table( + "catalog", + sa.Column("uuid", sqlalchemy_utils.types.uuid.UUIDType(), nullable=True), + sa.Column("created_at", sa.DateTime(timezone=True), nullable=True), + sa.Column("updated_at", sa.DateTime(timezone=True), nullable=True), + sa.Column("extra_params", sa.JSON(), nullable=True), + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.PrimaryKeyConstraint("id", name=op.f("pk_catalog")), + ) + op.create_table( + "database", + sa.Column("uuid", sqlalchemy_utils.types.uuid.UUIDType(), nullable=True), + sa.Column("name", sa.String(), nullable=True), + sa.Column("extra_params", sa.JSON(), nullable=True), + sa.Column("created_at", sa.DateTime(timezone=True), nullable=True), + sa.Column("updated_at", sa.DateTime(timezone=True), nullable=True), + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("description", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("URI", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("read_only", sa.Boolean(), nullable=False), + sa.Column("async", sa.Boolean(), nullable=False), + sa.Column("cost", sa.Float(), nullable=False), + sa.PrimaryKeyConstraint("id", name=op.f("pk_database")), + sa.UniqueConstraint("name", name=op.f("uq_database_name")), + ) + op.create_table( + "engine", + sa.Column( + "dialect", + sa.Enum("SPARK", "TRINO", "DRUID", name="dialect"), + nullable=True, + ), + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("version", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("uri", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.PrimaryKeyConstraint("id", name=op.f("pk_engine")), + ) + op.create_table( + "history", + sa.Column("pre", sa.JSON(), nullable=True), + sa.Column("post", sa.JSON(), nullable=True), + sa.Column("details", sa.JSON(), nullable=True), + sa.Column("created_at", sa.DateTime(timezone=True), nullable=True), + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("entity_type", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column("entity_name", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column("activity_type", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column("user", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.PrimaryKeyConstraint("id", name=op.f("pk_history")), + ) + op.create_table( + "missingparent", + sa.Column("name", sa.String(), nullable=True), + sa.Column("created_at", sa.DateTime(timezone=True), nullable=True), + sa.Column("id", sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint("id", name=op.f("pk_missingparent")), + ) + op.create_table( + "node", + sa.Column("name", sa.String(), nullable=True), + sa.Column( + "type", + sa.Enum( + "SOURCE", + "TRANSFORM", + "METRIC", + "DIMENSION", + "CUBE", + name="nodetype", + ), + nullable=True, + ), + sa.Column("display_name", sa.String(), nullable=True), + sa.Column("created_at", sa.DateTime(timezone=True), nullable=True), + sa.Column("deactivated_at", sa.DateTime(timezone=True), nullable=True), + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("namespace", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column( + "current_version", + sqlmodel.sql.sqltypes.AutoString(), + nullable=False, + ), + sa.PrimaryKeyConstraint("id", name=op.f("pk_node")), + sa.UniqueConstraint("name", "namespace", name="unique_node_namespace_name"), + sa.UniqueConstraint("name", name=op.f("uq_node_name")), + ) + op.create_table( + "nodenamespace", + sa.Column("namespace", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.PrimaryKeyConstraint("namespace", name=op.f("pk_nodenamespace")), + sa.UniqueConstraint("namespace", name=op.f("uq_nodenamespace_namespace")), + ) + op.create_table( + "tag", + sa.Column("tag_metadata", sa.JSON(), nullable=True), + sa.Column("name", sa.String(), nullable=True), + sa.Column("display_name", sa.String(), nullable=True), + sa.Column("description", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("tag_type", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("id", sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint("id", name=op.f("pk_tag")), + sa.UniqueConstraint("name", name=op.f("uq_tag_name")), + ) + op.create_table( + "catalogengines", + sa.Column("catalog_id", sa.Integer(), nullable=False), + sa.Column("engine_id", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint( + ["catalog_id"], + ["catalog.id"], + name=op.f("fk_catalogengines_catalog_id_catalog"), + ), + sa.ForeignKeyConstraint( + ["engine_id"], + ["engine.id"], + name=op.f("fk_catalogengines_engine_id_engine"), + ), + sa.PrimaryKeyConstraint( + "catalog_id", + "engine_id", + name=op.f("pk_catalogengines"), + ), + ) + op.create_table( + "column", + sa.Column("type", sa.String(), nullable=False), + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("dimension_id", sa.Integer(), nullable=True), + sa.Column( + "dimension_column", + sqlmodel.sql.sqltypes.AutoString(), + nullable=True, + ), + sa.ForeignKeyConstraint( + ["dimension_id"], + ["node.id"], + name=op.f("fk_column_dimension_id_node"), + ), + sa.PrimaryKeyConstraint("id", name=op.f("pk_column")), + ) + op.create_table( + "noderevision", + sa.Column("name", sa.String(), nullable=True), + sa.Column("display_name", sa.String(), nullable=True), + sa.Column( + "type", + sa.Enum( + "SOURCE", + "TRANSFORM", + "METRIC", + "DIMENSION", + "CUBE", + name="nodetype", + ), + nullable=True, + ), + sa.Column("updated_at", sa.DateTime(timezone=True), nullable=True), + sa.Column("description", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("query", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column("mode", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("version", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column("node_id", sa.Integer(), nullable=True), + sa.Column("catalog_id", sa.Integer(), nullable=True), + sa.Column("schema_", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column("table", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column("status", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.ForeignKeyConstraint( + ["catalog_id"], + ["catalog.id"], + name=op.f("fk_noderevision_catalog_id_catalog"), + ), + sa.ForeignKeyConstraint( + ["node_id"], + ["node.id"], + name=op.f("fk_noderevision_node_id_node"), + ), + sa.PrimaryKeyConstraint("id", name=op.f("pk_noderevision")), + sa.UniqueConstraint("version", "node_id", name=op.f("uq_noderevision_version")), + ) + op.create_table( + "table", + sa.Column("schema_", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column("table", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("cost", sa.Float(), nullable=False), + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("database_id", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint( + ["database_id"], + ["database.id"], + name=op.f("fk_table_database_id_database"), + ), + sa.PrimaryKeyConstraint("id", name=op.f("pk_table")), + ) + op.create_table( + "tagnoderelationship", + sa.Column("tag_id", sa.Integer(), nullable=False), + sa.Column("node_id", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint( + ["node_id"], + ["node.id"], + name=op.f("fk_tagnoderelationship_node_id_node"), + ), + sa.ForeignKeyConstraint( + ["tag_id"], + ["tag.id"], + name=op.f("fk_tagnoderelationship_tag_id_tag"), + ), + sa.PrimaryKeyConstraint( + "tag_id", + "node_id", + name=op.f("pk_tagnoderelationship"), + ), + ) + op.create_table( + "columnattribute", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("attribute_type_id", sa.Integer(), nullable=True), + sa.Column("column_id", sa.Integer(), nullable=True), + sa.ForeignKeyConstraint( + ["attribute_type_id"], + ["attributetype.id"], + name=op.f("fk_columnattribute_attribute_type_id_attributetype"), + ), + sa.ForeignKeyConstraint( + ["column_id"], + ["column.id"], + name=op.f("fk_columnattribute_column_id_column"), + ), + sa.PrimaryKeyConstraint("id", name=op.f("pk_columnattribute")), + sa.UniqueConstraint( + "attribute_type_id", + "column_id", + name=op.f("uq_columnattribute_attribute_type_id"), + ), + ) + op.create_table( + "cube", + sa.Column("cube_id", sa.Integer(), nullable=False), + sa.Column("cube_element_id", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint( + ["cube_element_id"], + ["column.id"], + name=op.f("fk_cube_cube_element_id_column"), + ), + sa.ForeignKeyConstraint( + ["cube_id"], + ["noderevision.id"], + name=op.f("fk_cube_cube_id_noderevision"), + ), + sa.PrimaryKeyConstraint("cube_id", "cube_element_id", name=op.f("pk_cube")), + ) + op.create_table( + "materialization", + sa.Column("config", sa.JSON(), nullable=True), + sa.Column("job", sa.String(), nullable=True), + sa.Column("node_revision_id", sa.Integer(), nullable=False), + sa.Column("engine_id", sa.Integer(), nullable=False), + sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("schedule", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.ForeignKeyConstraint( + ["engine_id"], + ["engine.id"], + name=op.f("fk_materialization_engine_id_engine"), + ), + sa.ForeignKeyConstraint( + ["node_revision_id"], + ["noderevision.id"], + name=op.f("fk_materialization_node_revision_id_noderevision"), + ), + sa.PrimaryKeyConstraint( + "node_revision_id", + "engine_id", + "name", + name=op.f("pk_materialization"), + ), + ) + op.create_table( + "nodeavailabilitystate", + sa.Column("availability_id", sa.Integer(), nullable=False), + sa.Column("node_id", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint( + ["availability_id"], + ["availabilitystate.id"], + name=op.f("fk_nodeavailabilitystate_availability_id_availabilitystate"), + ), + sa.ForeignKeyConstraint( + ["node_id"], + ["noderevision.id"], + name=op.f("fk_nodeavailabilitystate_node_id_noderevision"), + ), + sa.PrimaryKeyConstraint( + "availability_id", + "node_id", + name=op.f("pk_nodeavailabilitystate"), + ), + ) + op.create_table( + "nodecolumns", + sa.Column("node_id", sa.Integer(), nullable=False), + sa.Column("column_id", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint( + ["column_id"], + ["column.id"], + name=op.f("fk_nodecolumns_column_id_column"), + ), + sa.ForeignKeyConstraint( + ["node_id"], + ["noderevision.id"], + name=op.f("fk_nodecolumns_node_id_noderevision"), + ), + sa.PrimaryKeyConstraint("node_id", "column_id", name=op.f("pk_nodecolumns")), + ) + op.create_table( + "nodemissingparents", + sa.Column("missing_parent_id", sa.Integer(), nullable=False), + sa.Column("referencing_node_id", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint( + ["missing_parent_id"], + ["missingparent.id"], + name=op.f("fk_nodemissingparents_missing_parent_id_missingparent"), + ), + sa.ForeignKeyConstraint( + ["referencing_node_id"], + ["noderevision.id"], + name=op.f("fk_nodemissingparents_referencing_node_id_noderevision"), + ), + sa.PrimaryKeyConstraint( + "missing_parent_id", + "referencing_node_id", + name=op.f("pk_nodemissingparents"), + ), + ) + op.create_table( + "noderelationship", + sa.Column("parent_id", sa.Integer(), nullable=False), + sa.Column("parent_version", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column("child_id", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint( + ["child_id"], + ["noderevision.id"], + name=op.f("fk_noderelationship_child_id_noderevision"), + ), + sa.ForeignKeyConstraint( + ["parent_id"], + ["node.id"], + name=op.f("fk_noderelationship_parent_id_node"), + ), + sa.PrimaryKeyConstraint( + "parent_id", + "child_id", + name=op.f("pk_noderelationship"), + ), + ) + op.create_table( + "tablecolumns", + sa.Column("table_id", sa.Integer(), nullable=False), + sa.Column("column_id", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint( + ["column_id"], + ["column.id"], + name=op.f("fk_tablecolumns_column_id_column"), + ), + sa.ForeignKeyConstraint( + ["table_id"], + ["table.id"], + name=op.f("fk_tablecolumns_table_id_table"), + ), + sa.PrimaryKeyConstraint("table_id", "column_id", name=op.f("pk_tablecolumns")), + ) + + +def downgrade(): + op.drop_table("tablecolumns") + op.drop_table("noderelationship") + op.drop_table("nodemissingparents") + op.drop_table("nodecolumns") + op.drop_table("nodeavailabilitystate") + op.drop_table("materialization") + op.drop_table("cube") + op.drop_table("columnattribute") + op.drop_table("tagnoderelationship") + op.drop_table("table") + op.drop_table("noderevision") + op.drop_table("column") + op.drop_table("catalogengines") + op.drop_table("tag") + op.drop_table("nodenamespace") + op.drop_table("node") + op.drop_table("missingparent") + op.drop_table("history") + op.drop_table("engine") + op.drop_table("database") + op.drop_table("catalog") + op.drop_table("availabilitystate") + op.drop_table("attributetype") diff --git a/datajunction-server/alembic/versions/2023_07_11_2337-bd313a10e2a8_add_bound_dimensions_attribute_to_.py b/datajunction-server/alembic/versions/2023_07_11_2337-bd313a10e2a8_add_bound_dimensions_attribute_to_.py new file mode 100644 index 000000000..d4119673f --- /dev/null +++ b/datajunction-server/alembic/versions/2023_07_11_2337-bd313a10e2a8_add_bound_dimensions_attribute_to_.py @@ -0,0 +1,50 @@ +"""add required_dimensions attribute to NodeRevision + +Revision ID: bd313a10e2a8 +Revises: 4e1ff36c27c6 +Create Date: 2023-07-11 23:37:50.924139+00:00 + +""" +# pylint: disable=no-member, invalid-name, missing-function-docstring, unused-import, no-name-in-module + +import sqlalchemy as sa +import sqlmodel + +from alembic import op + +# revision identifiers, used by Alembic. +revision = "bd313a10e2a8" +down_revision = "4e1ff36c27c6" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "metric_required_dimensions", + sa.Column("metric_id", sa.Integer(), nullable=False), + sa.Column("bound_dimension_id", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint( + ["bound_dimension_id"], + ["column.id"], + name=op.f("fk_metric_required_dimensions_bound_dimension_id_column"), + ), + sa.ForeignKeyConstraint( + ["metric_id"], + ["noderevision.id"], + name=op.f("fk_metric_required_dimensions_metric_id_noderevision"), + ), + sa.PrimaryKeyConstraint( + "metric_id", + "bound_dimension_id", + name=op.f("pk_metric_required_dimensions"), + ), + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table("metric_required_dimensions") + # ### end Alembic commands ### diff --git a/datajunction-server/alembic/versions/2023_07_14_0428-5c3d0c958c3c_add_node_to_history.py b/datajunction-server/alembic/versions/2023_07_14_0428-5c3d0c958c3c_add_node_to_history.py new file mode 100644 index 000000000..75d0481f7 --- /dev/null +++ b/datajunction-server/alembic/versions/2023_07_14_0428-5c3d0c958c3c_add_node_to_history.py @@ -0,0 +1,30 @@ +"""Add node to history + +Revision ID: 5c3d0c958c3c +Revises: 4e1ff36c27c6 +Create Date: 2023-07-14 04:28:11.334887+00:00 + +""" +# pylint: disable=no-member, invalid-name, missing-function-docstring, unused-import, no-name-in-module + +import sqlalchemy as sa +import sqlmodel + +from alembic import op + +# revision identifiers, used by Alembic. +revision = "5c3d0c958c3c" +down_revision = "bd313a10e2a8" +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column( + "history", + sa.Column("node", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + ) + + +def downgrade(): + op.drop_column("history", "node") diff --git a/datajunction-server/alembic/versions/2023_07_30_1608-4147da2ac841_add_deactivated_to_materialization.py b/datajunction-server/alembic/versions/2023_07_30_1608-4147da2ac841_add_deactivated_to_materialization.py new file mode 100644 index 000000000..e3e20a8cc --- /dev/null +++ b/datajunction-server/alembic/versions/2023_07_30_1608-4147da2ac841_add_deactivated_to_materialization.py @@ -0,0 +1,30 @@ +"""Add deactivated to materialization + +Revision ID: 4147da2ac841 +Revises: 5c3d0c958c3c +Create Date: 2023-07-30 16:08:40.416585+00:00 + +""" +# pylint: disable=no-member, invalid-name, missing-function-docstring, unused-import, no-name-in-module + +import sqlalchemy as sa +import sqlmodel + +from alembic import op + +# revision identifiers, used by Alembic. +revision = "4147da2ac841" +down_revision = "5c3d0c958c3c" +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column( + "materialization", + sa.Column("deactivated_at", sa.DateTime(timezone=True), nullable=True), + ) + + +def downgrade(): + op.drop_column("materialization", "deactivated_at") diff --git a/datajunction-server/alembic/versions/2023_08_02_0055-ccc77abcf899_add_deactivated_to_namespace.py b/datajunction-server/alembic/versions/2023_08_02_0055-ccc77abcf899_add_deactivated_to_namespace.py new file mode 100644 index 000000000..125e09012 --- /dev/null +++ b/datajunction-server/alembic/versions/2023_08_02_0055-ccc77abcf899_add_deactivated_to_namespace.py @@ -0,0 +1,34 @@ +"""Add deactivated to namespace + +Revision ID: ccc77abcf899 +Revises: 4147da2ac841 +Create Date: 2023-08-02 00:55:12.995328+00:00 + +""" +# pylint: disable=no-member, invalid-name, missing-function-docstring, unused-import, no-name-in-module + +import sqlalchemy as sa +import sqlmodel + +from alembic import op + +# revision identifiers, used by Alembic. +revision = "ccc77abcf899" +down_revision = "4147da2ac841" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "nodenamespace", + sa.Column("deactivated_at", sa.DateTime(timezone=True), nullable=True), + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("nodenamespace", "deactivated_at") + # ### end Alembic commands ### diff --git a/datajunction-server/alembic/versions/2023_08_08_1856-cde75f986a62_user_model.py b/datajunction-server/alembic/versions/2023_08_08_1856-cde75f986a62_user_model.py new file mode 100644 index 000000000..abf0d93d6 --- /dev/null +++ b/datajunction-server/alembic/versions/2023_08_08_1856-cde75f986a62_user_model.py @@ -0,0 +1,45 @@ +"""User model + +Revision ID: cde75f986a62 +Revises: ccc77abcf899 +Create Date: 2023-08-08 18:56:40.033728+00:00 + +""" +# pylint: disable=no-member, invalid-name, missing-function-docstring, unused-import, no-name-in-module + +import sqlalchemy as sa +import sqlmodel + +from alembic import op + +# revision identifiers, used by Alembic. +revision = "cde75f986a62" +down_revision = "ccc77abcf899" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "users", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("username", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("password", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column("email", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column( + "oauth_provider", + sa.Enum("BASIC", "GITHUB", name="oauthprovider"), + nullable=False, + ), + sa.Column("is_admin", sa.Boolean(), nullable=False), + sa.PrimaryKeyConstraint("id", name=op.f("pk_users")), + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table("users") + # ### end Alembic commands ### diff --git a/datajunction-server/datajunction_server/__about__.py b/datajunction-server/datajunction_server/__about__.py new file mode 100644 index 000000000..d0996dbb6 --- /dev/null +++ b/datajunction-server/datajunction_server/__about__.py @@ -0,0 +1,4 @@ +""" +Version for Hatch +""" +__version__ = "0.0.1a18" diff --git a/datajunction-server/datajunction_server/__init__.py b/datajunction-server/datajunction_server/__init__.py new file mode 100644 index 000000000..2cff295fb --- /dev/null +++ b/datajunction-server/datajunction_server/__init__.py @@ -0,0 +1,14 @@ +""" +Package version and name. +""" + +from importlib.metadata import PackageNotFoundError, version # pragma: no cover + +try: + # Change here if project is renamed and does not equal the package name + DIST_NAME = __name__ + __version__ = version(DIST_NAME) +except PackageNotFoundError: # pragma: no cover + __version__ = "unknown" +finally: + del version, PackageNotFoundError diff --git a/datajunction-server/datajunction_server/api/__init__.py b/datajunction-server/datajunction_server/api/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/datajunction-server/datajunction_server/api/attributes.py b/datajunction-server/datajunction_server/api/attributes.py new file mode 100644 index 000000000..3bab679e9 --- /dev/null +++ b/datajunction-server/datajunction_server/api/attributes.py @@ -0,0 +1,136 @@ +""" +Attributes related APIs. +""" + +import logging +from typing import List + +from fastapi import APIRouter, Depends +from sqlmodel import Session, select + +from datajunction_server.errors import DJAlreadyExistsException, DJException +from datajunction_server.models.attribute import ( + RESERVED_ATTRIBUTE_NAMESPACE, + AttributeType, + MutableAttributeTypeFields, +) +from datajunction_server.models.node import NodeType +from datajunction_server.utils import get_session + +_logger = logging.getLogger(__name__) +router = APIRouter(tags=["attributes"]) + + +@router.get("/attributes/", response_model=List[AttributeType]) +def list_attributes(*, session: Session = Depends(get_session)) -> List[AttributeType]: + """ + List all available attribute types. + """ + return session.exec(select(AttributeType)).all() + + +@router.post( + "/attributes/", + response_model=AttributeType, + status_code=201, + name="Add an Attribute Type", +) +def add_attribute_type( + data: MutableAttributeTypeFields, *, session: Session = Depends(get_session) +) -> AttributeType: + """ + Add a new attribute type + """ + if data.namespace == RESERVED_ATTRIBUTE_NAMESPACE: + raise DJException( + message="Cannot use `system` as the attribute type namespace as it is reserved.", + ) + statement = select(AttributeType).where(AttributeType.name == data.name) + attribute_type = session.exec(statement).unique().one_or_none() + if attribute_type: + raise DJAlreadyExistsException( + message=f"Attribute type `{data.name}` already exists!", + ) + attribute_type = AttributeType.from_orm(data) + session.add(attribute_type) + session.commit() + session.refresh(attribute_type) + return attribute_type + + +def default_attribute_types(session: Session = Depends(get_session)): + """ + Loads all the column attribute types that are supported by the system + by default into the database. + """ + defaults = [ + AttributeType( + namespace=RESERVED_ATTRIBUTE_NAMESPACE, + name="primary_key", + description="Points to a column which is part of the primary key of the node", + uniqueness_scope=[], + allowed_node_types=[ + NodeType.SOURCE, + NodeType.TRANSFORM, + NodeType.DIMENSION, + ], + ), + AttributeType( + namespace=RESERVED_ATTRIBUTE_NAMESPACE, + name="event_time", + description="Points to a column which represents the time of the event in a given " + "fact related node. Used to facilitate proper joins with dimension node " + "to match the desired effect.", + uniqueness_scope=["node", "column_type"], + allowed_node_types=[NodeType.SOURCE, NodeType.TRANSFORM], + ), + AttributeType( + namespace=RESERVED_ATTRIBUTE_NAMESPACE, + name="effective_time", + description="Points to a column which represents the effective time of a row in a " + "dimension node. Used to facilitate proper joins with fact nodes" + " on event time.", + uniqueness_scope=["node", "column_type"], + allowed_node_types=[NodeType.DIMENSION], + ), + AttributeType( + namespace=RESERVED_ATTRIBUTE_NAMESPACE, + name="expired_time", + description="Points to a column which represents the expired time of a row in a " + "dimension node. Used to facilitate proper joins with fact nodes " + "on event time.", + uniqueness_scope=["node", "column_type"], + allowed_node_types=[NodeType.DIMENSION], + ), + AttributeType( + namespace=RESERVED_ATTRIBUTE_NAMESPACE, + name="dimension", + description="Points to a dimension attribute column", + uniqueness_scope=[], + allowed_node_types=[NodeType.SOURCE, NodeType.TRANSFORM], + ), + ] + default_attribute_type_names = {type_.name: type_ for type_ in defaults} + + # Update existing default attribute types + statement = select(AttributeType).filter( + # pylint: disable=no-member + AttributeType.name.in_( # type: ignore + set(default_attribute_type_names.keys()), + ), + ) + attribute_types = session.exec(statement).all() + for type_ in attribute_types: + updated_type = default_attribute_type_names[type_.name].dict( + exclude_unset=True, + ) + type_ = type_.update(updated_type) + session.add(type_) + + # Add new default attribute types + new_types = set(default_attribute_type_names.keys()) - { + type_.name for type_ in attribute_types + } + for name in new_types: + session.add(default_attribute_type_names[name]) + session.commit() diff --git a/datajunction-server/datajunction_server/api/authentication/__init__.py b/datajunction-server/datajunction_server/api/authentication/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/datajunction-server/datajunction_server/api/authentication/basic.py b/datajunction-server/datajunction_server/api/authentication/basic.py new file mode 100644 index 000000000..200d78a44 --- /dev/null +++ b/datajunction-server/datajunction_server/api/authentication/basic.py @@ -0,0 +1,84 @@ +""" +Basic OAuth Authentication Router +""" +from http import HTTPStatus + +from fastapi import APIRouter, Depends, Form, Request +from fastapi.responses import JSONResponse +from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm +from sqlmodel import Session, select + +from datajunction_server.errors import DJError, DJException, ErrorCode +from datajunction_server.internal.authentication.basic import ( + get_password_hash, + get_user_info, +) +from datajunction_server.internal.authentication.jwt import create_jwt, encrypt +from datajunction_server.models.user import OAuthProvider, User, UserOutput +from datajunction_server.utils import get_session + +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/basic/login/") +router = APIRouter(tags=["Basic OAuth2"]) + + +@router.post("/basic/user/") +async def create_a_user( + username: str = Form(), + password: str = Form(), + session: Session = Depends(get_session), +) -> JSONResponse: + """ + Create a new user + """ + if session.exec(select(User).where(User.username == username)).one_or_none(): + raise DJException( + http_status_code=HTTPStatus.CONFLICT, + errors=[ + DJError( + code=ErrorCode.ALREADY_EXISTS, + message=f"User {username} already exists.", + ), + ], + ) + new_user = User( + username=username, + password=get_password_hash(password), + oauth_provider=OAuthProvider.BASIC, + ) + session.add(new_user) + session.commit() + session.refresh(new_user) + return JSONResponse( + content={"message": "User successfully created"}, + status_code=HTTPStatus.CREATED, + ) + + +@router.post("/basic/login/") +async def login( + form_data: OAuth2PasswordRequestForm = Depends(), + session: Session = Depends(get_session), +): + """ + Get a JWT token and set it as an HTTP only cookie + """ + user = get_user_info( + username=form_data.username, + password=form_data.password, + session=session, + ) + jwt = create_jwt(data={"sub": encrypt(user.username)}) + response = JSONResponse( + content={"message": "Successfully logged in through basic OAuth"}, + status_code=HTTPStatus.OK, + ) + response.set_cookie(key="__dj", value=jwt, httponly=True, samesite="strict") + return response + + +@router.get("/basic/whoami/", response_model=UserOutput) +async def get_current_user(request: Request) -> UserOutput: + """ + Returns the current authenticated user + """ + return request.state.user diff --git a/datajunction-server/datajunction_server/api/authentication/github.py b/datajunction-server/datajunction_server/api/authentication/github.py new file mode 100644 index 000000000..7c9fd1d63 --- /dev/null +++ b/datajunction-server/datajunction_server/api/authentication/github.py @@ -0,0 +1,99 @@ +""" +GitHub OAuth Authentication Router +""" +import logging +from http import HTTPStatus + +import requests +from fastapi import APIRouter, Request, Response +from starlette.responses import JSONResponse, RedirectResponse + +from datajunction_server.errors import DJError, DJException, ErrorCode +from datajunction_server.internal.authentication import github +from datajunction_server.internal.authentication.jwt import create_jwt, encrypt +from datajunction_server.models.user import UserOutput +from datajunction_server.utils import get_settings + +_logger = logging.getLogger(__name__) +router = APIRouter(tags=["GitHub OAuth2"]) + + +@router.get("/github/login/", status_code=HTTPStatus.FOUND) +async def login() -> RedirectResponse: # pragma: no cover + """ + Login + """ + settings = get_settings() + if not settings.github_oauth_client_id: + raise DJException( + http_status_code=HTTPStatus.NOT_IMPLEMENTED, + errors=[ + DJError( + code=ErrorCode.OAUTH_ERROR, + message="GITHUB_OAUTH_CLIENT_ID is not set", + ), + ], + ) + return RedirectResponse( + url=github.get_authorize_url(oauth_client_id=settings.github_oauth_client_id), + status_code=HTTPStatus.FOUND, + ) + + +@router.get("/github/token/") +async def get_access_token( + code: str, + response: Response, +) -> JSONResponse: # pragma: no cover + """ + Get an access token using OAuth code + """ + settings = get_settings() + params = { + "client_id": settings.github_oauth_client_id, + "client_secret": settings.github_oauth_client_secret, + "code": code, + } + headers = {"Accept": "application/json"} + access_data = requests.post( + url="https://github.com/login/oauth/access_token", + params=params, + headers=headers, + timeout=10, # seconds + ).json() + if "error" in access_data: + raise DJException( + http_status_code=HTTPStatus.UNAUTHORIZED, + errors=[ + DJError( + code=ErrorCode.OAUTH_ERROR, + message=( + "Received an error from the GitHub authorization " + f"server: {access_data['error']}" + ), + ), + ], + ) + if "access_token" not in access_data: + message = "No user access token retrieved from GitHub OAuth API" + _logger.error(message) + raise DJException( + http_status_code=HTTPStatus.UNAUTHORIZED, + errors=[DJError(message=message, code=ErrorCode.OAUTH_ERROR)], + ) + token = access_data["access_token"] + response = JSONResponse( + content={"message": "Successfully logged in through GitHub OAuth"}, + status_code=HTTPStatus.OK, + ) + jwt = create_jwt({"sub": encrypt(token)}) + response.set_cookie(key="__dj", value=jwt, httponly=True, samesite="strict") + return response + + +@router.get("/github/whoami/", response_model=UserOutput) +async def get_current_user(request: Request) -> UserOutput: # pragma: no cover + """ + Returns the current authenticated user + """ + return request.state.user diff --git a/datajunction-server/datajunction_server/api/catalogs.py b/datajunction-server/datajunction_server/api/catalogs.py new file mode 100644 index 000000000..f3daa1e8a --- /dev/null +++ b/datajunction-server/datajunction_server/api/catalogs.py @@ -0,0 +1,119 @@ +""" +Catalog related APIs. +""" + +import logging +from http import HTTPStatus +from typing import List + +from fastapi import APIRouter, Depends, HTTPException +from sqlmodel import Session, select + +from datajunction_server.api.engines import EngineInfo, get_engine +from datajunction_server.api.helpers import get_catalog_by_name +from datajunction_server.errors import DJException +from datajunction_server.models.catalog import Catalog, CatalogInfo +from datajunction_server.utils import get_session + +_logger = logging.getLogger(__name__) +router = APIRouter(tags=["catalogs"]) + + +@router.get("/catalogs/", response_model=List[CatalogInfo]) +def list_catalogs(*, session: Session = Depends(get_session)) -> List[CatalogInfo]: + """ + List all available catalogs + """ + return list(session.exec(select(Catalog))) + + +@router.get("/catalogs/{name}/", response_model=CatalogInfo, name="Get a Catalog") +def get_catalog(name: str, *, session: Session = Depends(get_session)) -> CatalogInfo: + """ + Return a catalog by name + """ + return get_catalog_by_name(session, name) + + +@router.post( + "/catalogs/", + response_model=CatalogInfo, + status_code=201, + name="Add A Catalog", +) +def add_catalog( + data: CatalogInfo, + *, + session: Session = Depends(get_session), +) -> CatalogInfo: + """ + Add a Catalog + """ + try: + get_catalog_by_name(session, data.name) + except DJException: + pass + else: + raise HTTPException( + status_code=HTTPStatus.CONFLICT, + detail=f"Catalog already exists: `{data.name}`", + ) + + catalog = Catalog.from_orm(data) + catalog.engines.extend( + list_new_engines( + session=session, + catalog=catalog, + create_engines=data.engines, + ), + ) + session.add(catalog) + session.commit() + session.refresh(catalog) + + return catalog + + +@router.post( + "/catalogs/{name}/engines/", + response_model=CatalogInfo, + status_code=201, + name="Add Engines to a Catalog", +) +def add_engines_to_catalog( + name: str, + data: List[EngineInfo], + *, + session: Session = Depends(get_session), +) -> CatalogInfo: + """ + Attach one or more engines to a catalog + """ + catalog = get_catalog_by_name(session, name) + catalog.engines.extend( + list_new_engines(session=session, catalog=catalog, create_engines=data), + ) + session.add(catalog) + session.commit() + session.refresh(catalog) + return catalog + + +def list_new_engines( + session: Session, + catalog: Catalog, + create_engines: List[EngineInfo], +) -> List[EngineInfo]: + """ + Filter to engines that are not already set on a catalog + """ + new_engines = [] + for engine_ref in create_engines: + already_set = False + engine = get_engine(session, engine_ref.name, engine_ref.version) + for set_engine in catalog.engines: + if engine.name == set_engine.name and engine.version == set_engine.version: + already_set = True + if not already_set: + new_engines.append(engine) + return new_engines diff --git a/datajunction-server/datajunction_server/api/client.py b/datajunction-server/datajunction_server/api/client.py new file mode 100644 index 000000000..4308d69f6 --- /dev/null +++ b/datajunction-server/datajunction_server/api/client.py @@ -0,0 +1,161 @@ +""" +APIs related to generating client code used for performing various actions in DJ. +""" + +import json +import logging + +from fastapi import APIRouter, Depends +from sqlmodel import Session + +from datajunction_server.api.helpers import get_node_by_name +from datajunction_server.models.node import NodeType +from datajunction_server.utils import get_session + +_logger = logging.getLogger(__name__) +router = APIRouter(tags=["client"]) + + +@router.get("/datajunction-clients/python/new_node/{node_name}", response_model=str) +def client_code_for_creating_node( + node_name: str, *, session: Session = Depends(get_session) +) -> str: + """ + Generate the Python client code used for creating this node + """ + node_short_name = node_name.split(".")[-1] + node = get_node_by_name(session, node_name) + + # Generic user-configurable node creation params + params = node.current.dict( + exclude={ + "id", + "version", + "type", + "catalog_id", + "status", + "mode", + "node_id", + "updated_at", + "query" if node.type == NodeType.CUBE else "", + }, + exclude_none=True, + ) + + params["primary_key"] = [col.name for col in node.current.primary_key()] + + for key in params: + if not isinstance(params[key], list) and key != "query": + params[key] = f'"{params[key]}"' + if key == "query": + params[key] = f'"""{params[key]}"""' + + # Cube-specific params + cube_params = [] + if node.type == NodeType.CUBE: + cube_metrics = ", ".join( + [ + '"' + elem.node_revisions[-1].name + '"' + for elem in node.current.cube_elements + if elem.node_revisions[-1].type == NodeType.METRIC + ], + ) + cube_dimensions = ", ".join( + [ + '"' + elem.node_revisions[-1].name + "." + elem.name + '"' + for elem in node.current.cube_elements + if elem.node_revisions[-1].type == NodeType.DIMENSION + ], + ) + cube_params = [ + f" metrics=[{cube_metrics}]", + f" dimensions=[{cube_dimensions}]", + ] + + formatted_params = ",\n".join( + [f" {k}={params[k]}" for k in sorted(params.keys())] + cube_params, + ) + + client_code = f"""dj = DJBuilder(DJ_URL) + +{node_short_name} = dj.create_{node.type}( +{formatted_params} +)""" + return client_code # type: ignore + + +@router.get( + "/datajunction-clients/python/add_materialization/{node_name}/{materialization_name}", + response_model=str, +) +def client_code_for_adding_materialization( + node_name: str, + materialization_name: str, + *, + session: Session = Depends(get_session), +) -> str: + """ + Generate the Python client code used for adding this materialization + """ + node_short_name = node_name.split(".")[-1] + node = get_node_by_name(session, node_name) + materialization = [ + materialization + for materialization in node.current.materializations + if materialization.name == materialization_name + ][0] + user_modified_config = { + key: materialization.config[key] + for key in materialization.config + if key in ("partitions", "spark", "druid", "") + } + with_b = "\n".join( + [ + f" {line}" + for line in json.dumps(user_modified_config, indent=4).split("\n") + ], + ) + client_code = f"""dj = DJBuilder(DJ_URL) + +{node_short_name} = dj.{node.type}( + "{node.name}" +) +materialization = MaterializationConfig( + engine=Engine( + name="{materialization.engine.name}", + version="{materialization.engine.version}", + ), + schedule="{materialization.schedule}", + config={with_b.strip()}, +) +{node_short_name}.add_materialization( + materialization +)""" + return client_code # type: ignore + + +@router.get( + "/datajunction-clients/python/link_dimension/{node_name}/{column}/{dimension}/", + response_model=str, +) +def client_code_for_linking_dimension_to_node( + node_name: str, + column: str, + dimension: str, + *, + session: Session = Depends(get_session), +) -> str: + """ + Generate the Python client code used for linking this node's column to a dimension + """ + node_short_name = node_name.split(".")[-1] + node = get_node_by_name(session, node_name) + client_code = f"""dj = DJBuilder(DJ_URL) +{node_short_name} = dj.{node.type}( + "{node.name}" +) +{node_short_name}.link_dimension( + "{column}", + "{dimension}", +)""" + return client_code # type: ignore diff --git a/datajunction-server/datajunction_server/api/cubes.py b/datajunction-server/datajunction_server/api/cubes.py new file mode 100644 index 000000000..467df3c5c --- /dev/null +++ b/datajunction-server/datajunction_server/api/cubes.py @@ -0,0 +1,26 @@ +""" +Cube related APIs. +""" +import logging + +from fastapi import APIRouter, Depends +from sqlmodel import Session + +from datajunction_server.api.helpers import get_node_by_name +from datajunction_server.models.cube import CubeRevisionMetadata +from datajunction_server.models.node import NodeType +from datajunction_server.utils import get_session + +_logger = logging.getLogger(__name__) +router = APIRouter(tags=["cubes"]) + + +@router.get("/cubes/{name}/", response_model=CubeRevisionMetadata, name="Get a Cube") +def get_cube( + name: str, *, session: Session = Depends(get_session) +) -> CubeRevisionMetadata: + """ + Get information on a cube + """ + node = get_node_by_name(session=session, name=name, node_type=NodeType.CUBE) + return node.current diff --git a/datajunction-server/datajunction_server/api/data.py b/datajunction-server/datajunction_server/api/data.py new file mode 100644 index 000000000..1caa288b0 --- /dev/null +++ b/datajunction-server/datajunction_server/api/data.py @@ -0,0 +1,290 @@ +""" +Data related APIs. +""" +from http import HTTPStatus +from typing import List, Optional + +from fastapi import APIRouter, Depends, Query, Request +from fastapi.responses import JSONResponse +from sqlmodel import Session +from sse_starlette.sse import EventSourceResponse + +from datajunction_server.api.helpers import ( + build_sql_for_multiple_metrics, + get_engine, + get_node_by_name, + get_query, + query_event_stream, + validate_orderby, +) +from datajunction_server.errors import ( + DJException, + DJInvalidInputException, + DJQueryServiceClientException, +) +from datajunction_server.models import History +from datajunction_server.models.history import ActivityType, EntityType +from datajunction_server.models.metric import TranslatedSQL +from datajunction_server.models.node import ( + AvailabilityState, + AvailabilityStateBase, + NodeType, +) +from datajunction_server.models.query import ( + ColumnMetadata, + QueryCreate, + QueryWithResults, +) +from datajunction_server.service_clients import QueryServiceClient +from datajunction_server.utils import get_query_service_client, get_session + +router = APIRouter(tags=["data"]) + + +@router.post("/data/{node_name}/availability/", name="Add Availability State to Node") +def add_availability_state( + node_name: str, + data: AvailabilityStateBase, + *, + session: Session = Depends(get_session), +) -> JSONResponse: + """ + Add an availability state to a node + """ + node = get_node_by_name(session, node_name) + + # Source nodes require that any availability states set are for one of the defined tables + node_revision = node.current + if node.current.type == NodeType.SOURCE: + if ( + data.catalog != node_revision.catalog.name + or node_revision.schema_ != data.schema_ + or node_revision.table != data.table + ): + raise DJException( + message=( + "Cannot set availability state, " + "source nodes require availability " + "states to match the set table: " + f"{data.catalog}." + f"{data.schema_}." + f"{data.table} " + "does not match " + f"{node_revision.catalog.name}." + f"{node_revision.schema_}." + f"{node_revision.table} " + ), + ) + + # Merge the new availability state with the current availability state if one exists + old_availability = node_revision.availability + if ( + node_revision.availability + and node_revision.availability.catalog == node.current.catalog.name + and node_revision.availability.schema_ == data.schema_ + and node_revision.availability.table == data.table + ): + data.merge(node_revision.availability) + + # Update the node with the new availability state + node_revision.availability = AvailabilityState.from_orm(data) + if node_revision.availability and not node_revision.availability.partitions: + node_revision.availability.partitions = [] + session.add(node_revision) + session.add( + History( + entity_type=EntityType.AVAILABILITY, + node=node.name, + activity_type=ActivityType.CREATE, + pre=AvailabilityStateBase.parse_obj(old_availability).dict() + if old_availability + else {}, + post=AvailabilityStateBase.parse_obj(node_revision.availability).dict(), + ), + ) + session.commit() + return JSONResponse( + status_code=200, + content={"message": "Availability state successfully posted"}, + ) + + +@router.get("/data/{node_name}/", name="Get Data for a Node") +def get_data( # pylint: disable=too-many-locals + node_name: str, + *, + dimensions: List[str] = Query([], description="Dimensional attributes to group by"), + filters: List[str] = Query([], description="Filters on dimensional attributes"), + orderby: List[str] = Query([], description="Expression to order by"), + limit: Optional[int] = Query( + None, + description="Number of rows to limit the data retrieved to", + ), + async_: bool = Query( + default=False, + description="Whether to run the query async or wait for results from the query engine", + ), + session: Session = Depends(get_session), + query_service_client: QueryServiceClient = Depends(get_query_service_client), + engine_name: Optional[str] = None, + engine_version: Optional[str] = None, +) -> QueryWithResults: + """ + Gets data for a node + """ + node = get_node_by_name(session, node_name) + + available_engines = node.current.catalog.engines + engine = ( + get_engine(session, engine_name, engine_version) # type: ignore + if engine_name + else available_engines[0] + ) + if engine not in available_engines: + raise DJInvalidInputException( # pragma: no cover + f"The selected engine is not available for the node {node_name}. " + f"Available engines include: {', '.join(engine.name for engine in available_engines)}", + ) + validate_orderby(orderby, [node_name], dimensions) + query_ast = get_query( + session=session, + node_name=node_name, + dimensions=dimensions, + filters=filters, + orderby=orderby, + limit=limit, + engine=engine, + ) + columns = [ + ColumnMetadata(name=col.alias_or_name.name, type=str(col.type)) # type: ignore + for col in query_ast.select.projection + ] + query = TranslatedSQL( + sql=str(query_ast), + columns=columns, + ) + + query_create = QueryCreate( + engine_name=engine.name, + catalog_name=node.current.catalog.name, + engine_version=engine.version, + submitted_query=query.sql, + async_=async_, + ) + result = query_service_client.submit_query(query_create) + # Inject column info if there are results + if result.results.__root__: # pragma: no cover + result.results.__root__[0].columns = columns + return result + + +@router.get( + "/data/query/{query_id}", + response_model=QueryWithResults, + name="Get Data For Query ID", +) +def get_data_for_query( + query_id: str, + *, + query_service_client: QueryServiceClient = Depends(get_query_service_client), +) -> QueryWithResults: + """ + Return data for a specific query ID. + """ + try: + return query_service_client.get_query(query_id=query_id) + except DJQueryServiceClientException as exc: + raise DJException( + message=str(exc.message), + http_status_code=HTTPStatus.NOT_FOUND, + ) from exc + + +@router.get("/data/", response_model=QueryWithResults, name="Get Data For Metrics") +def get_data_for_metrics( # pylint: disable=R0914, R0913 + metrics: List[str] = Query([]), + dimensions: List[str] = Query([]), + filters: List[str] = Query([]), + orderby: List[str] = Query([]), + limit: Optional[int] = None, + async_: bool = False, + *, + session: Session = Depends(get_session), + query_service_client: QueryServiceClient = Depends(get_query_service_client), + engine_name: Optional[str] = None, + engine_version: Optional[str] = None, +) -> QueryWithResults: + """ + Return data for a set of metrics with dimensions and filters + """ + translated_sql, engine, catalog = build_sql_for_multiple_metrics( + session, + metrics, + dimensions, + filters, + orderby, + limit, + engine_name, + engine_version, + ) + + query_create = QueryCreate( + engine_name=engine.name, + catalog_name=catalog.name, + engine_version=engine.version, + submitted_query=translated_sql.sql, + async_=async_, + ) + result = query_service_client.submit_query(query_create) + + # Inject column info if there are results + if result.results.__root__: # pragma: no cover + result.results.__root__[0].columns = translated_sql.columns or [] + return result + + +@router.get("/stream/", response_model=QueryWithResults) +async def get_data_stream_for_metrics( # pylint: disable=R0914, R0913 + metrics: List[str] = Query([]), + dimensions: List[str] = Query([]), + filters: List[str] = Query([]), + orderby: List[str] = Query([]), + limit: Optional[int] = None, + *, + session: Session = Depends(get_session), + request: Request, + query_service_client: QueryServiceClient = Depends(get_query_service_client), + engine_name: Optional[str] = None, + engine_version: Optional[str] = None, +) -> QueryWithResults: + """ + Return data for a set of metrics with dimensions and filters using server side events + """ + translated_sql, engine, catalog = build_sql_for_multiple_metrics( + session, + metrics, + dimensions, + filters, + orderby, + limit, + engine_name, + engine_version, + ) + + query_create = QueryCreate( + engine_name=engine.name, + catalog_name=catalog.name, + engine_version=engine.version, + submitted_query=translated_sql.sql, + async_=True, + ) + # Submits the query, equivalent to calling POST /data/ directly + initial_query_info = query_service_client.submit_query(query_create) + return EventSourceResponse( + query_event_stream( + query=initial_query_info, + query_service_client=query_service_client, + columns=translated_sql.columns, # type: ignore + request=request, + ), + ) diff --git a/datajunction-server/datajunction_server/api/dimensions.py b/datajunction-server/datajunction_server/api/dimensions.py new file mode 100644 index 000000000..edfcafe09 --- /dev/null +++ b/datajunction-server/datajunction_server/api/dimensions.py @@ -0,0 +1,66 @@ +""" +Dimensions related APIs. +""" +import logging +from typing import List, Union + +from fastapi import APIRouter, Depends, Query +from sqlalchemy.sql.operators import is_ +from sqlmodel import Session, select +from typing_extensions import Annotated + +from datajunction_server.api.helpers import get_node_by_name +from datajunction_server.models.node import Node, NodeRevisionOutput, NodeType +from datajunction_server.sql.dag import ( + get_nodes_with_common_dimensions, + get_nodes_with_dimension, +) +from datajunction_server.utils import get_session + +_logger = logging.getLogger(__name__) +router = APIRouter(tags=["dimensions"]) + + +@router.get("/dimensions/", response_model=List[str]) +def list_dimensions(*, session: Session = Depends(get_session)) -> List[str]: + """ + List all available dimensions. + """ + return session.exec( + select(Node.name) + .where(Node.type == NodeType.DIMENSION) + .where(is_(Node.deactivated_at, None)), + ).all() + + +@router.get("/dimensions/{name}/nodes/", response_model=List[NodeRevisionOutput]) +def find_nodes_with_dimension( + name: str, + *, + node_type: Annotated[Union[List[NodeType], None], Query()] = Query(None), + session: Session = Depends(get_session), +) -> List[NodeRevisionOutput]: + """ + List all nodes that have the specified dimension + """ + dimension_node = get_node_by_name(session, name) + nodes = get_nodes_with_dimension(session, dimension_node, node_type) + return nodes + + +@router.get("/dimensions/common/", response_model=List[NodeRevisionOutput]) +def find_nodes_with_common_dimensions( + dimension: Annotated[Union[List[str], None], Query()] = Query(None), + node_type: Annotated[Union[List[NodeType], None], Query()] = Query(None), + *, + session: Session = Depends(get_session), +) -> List[NodeRevisionOutput]: + """ + Find all nodes that have the list of common dimensions + """ + nodes = get_nodes_with_common_dimensions( + session, + [get_node_by_name(session, dim) for dim in dimension], # type: ignore + node_type, + ) + return nodes diff --git a/datajunction-server/datajunction_server/api/djsql.py b/datajunction-server/datajunction_server/api/djsql.py new file mode 100644 index 000000000..18e4e2742 --- /dev/null +++ b/datajunction-server/datajunction_server/api/djsql.py @@ -0,0 +1,92 @@ +""" +Data related APIs. +""" + +from typing import Optional + +from fastapi import APIRouter, Depends, Request +from sqlmodel import Session +from sse_starlette.sse import EventSourceResponse + +from datajunction_server.api.helpers import build_sql_for_dj_query, query_event_stream +from datajunction_server.models.query import QueryCreate, QueryWithResults +from datajunction_server.service_clients import QueryServiceClient +from datajunction_server.utils import get_query_service_client, get_session + +router = APIRouter() + + +@router.get("/djsql/data", response_model=QueryWithResults) +def get_data_for_djsql( # pylint: disable=R0914, R0913 + query: str, + async_: bool = False, + *, + session: Session = Depends(get_session), + query_service_client: QueryServiceClient = Depends(get_query_service_client), + engine_name: Optional[str] = None, + engine_version: Optional[str] = None, +) -> QueryWithResults: + """ + Return data for a DJ SQL query + """ + translated_sql, engine, catalog = build_sql_for_dj_query( + session, + query, + engine_name, + engine_version, + ) + + query_create = QueryCreate( + engine_name=engine.name, + catalog_name=catalog.name, + engine_version=engine.version, + submitted_query=translated_sql.sql, + async_=async_, + ) + + result = query_service_client.submit_query(query_create) + + # Inject column info if there are results + if result.results.__root__: # pragma: no cover + result.results.__root__[0].columns = translated_sql.columns or [] + return result + + +# pylint: disable=R0914, R0913 +@router.get("/djsql/stream/", response_model=QueryWithResults) +async def get_data_stream_for_djsql( # pragma: no cover + query: str, + *, + session: Session = Depends(get_session), + request: Request, + query_service_client: QueryServiceClient = Depends(get_query_service_client), + engine_name: Optional[str] = None, + engine_version: Optional[str] = None, +) -> QueryWithResults: # pragma: no cover + """ + Return data for a DJ SQL query using server side events + """ + translated_sql, engine, catalog = build_sql_for_dj_query( + session, + query, + engine_name, + engine_version, + ) + + query_create = QueryCreate( + engine_name=engine.name, + catalog_name=catalog.name, + engine_version=engine.version, + submitted_query=translated_sql.sql, + async_=True, + ) + # Submits the query, equivalent to calling POST /data/ directly + initial_query_info = query_service_client.submit_query(query_create) + return EventSourceResponse( + query_event_stream( + query=initial_query_info, + query_service_client=query_service_client, + columns=translated_sql.columns, # type: ignore + request=request, + ), + ) diff --git a/datajunction-server/datajunction_server/api/engines.py b/datajunction-server/datajunction_server/api/engines.py new file mode 100644 index 000000000..411852cf2 --- /dev/null +++ b/datajunction-server/datajunction_server/api/engines.py @@ -0,0 +1,65 @@ +""" +Engine related APIs. +""" + +from http import HTTPStatus +from typing import List + +from fastapi import APIRouter, Depends, HTTPException +from sqlmodel import Session, select + +from datajunction_server.api.helpers import get_engine +from datajunction_server.models.engine import Engine, EngineInfo +from datajunction_server.utils import get_session + +router = APIRouter(tags=["engines"]) + + +@router.get("/engines/", response_model=List[EngineInfo]) +def list_engines(*, session: Session = Depends(get_session)) -> List[EngineInfo]: + """ + List all available engines + """ + return list(session.exec(select(Engine))) + + +@router.get("/engines/{name}/{version}/", response_model=EngineInfo) +def get_an_engine( + name: str, version: str, *, session: Session = Depends(get_session) +) -> EngineInfo: + """ + Return an engine by name and version + """ + return get_engine(session, name, version) + + +@router.post( + "/engines/", + response_model=EngineInfo, + status_code=201, + name="Add An Engine", +) +def add_engine( + data: EngineInfo, + *, + session: Session = Depends(get_session), +) -> EngineInfo: + """ + Add a new engine + """ + try: + get_engine(session, data.name, data.version) + except HTTPException: + pass + else: + raise HTTPException( + status_code=HTTPStatus.CONFLICT, + detail=f"Engine already exists: `{data.name}` version `{data.version}`", + ) + + engine = Engine.from_orm(data) + session.add(engine) + session.commit() + session.refresh(engine) + + return engine diff --git a/datajunction-server/datajunction_server/api/graphql/__init__.py b/datajunction-server/datajunction_server/api/graphql/__init__.py new file mode 100644 index 000000000..1f96c11e1 --- /dev/null +++ b/datajunction-server/datajunction_server/api/graphql/__init__.py @@ -0,0 +1,3 @@ +""" +DJ graphql api +""" diff --git a/datajunction-server/datajunction_server/api/graphql/catalogs.py b/datajunction-server/datajunction_server/api/graphql/catalogs.py new file mode 100644 index 000000000..6af60ff9a --- /dev/null +++ b/datajunction-server/datajunction_server/api/graphql/catalogs.py @@ -0,0 +1,35 @@ +""" +Catalog related APIs. +""" + +from typing import List + +import strawberry +from sqlmodel import select +from strawberry.types import Info + +from datajunction_server.models.catalog import Catalog +from datajunction_server.models.catalog import CatalogInfo as _CatalogInfo + +from .engines import EngineInfo # pylint: disable=W0611 + + +@strawberry.experimental.pydantic.type(model=_CatalogInfo, all_fields=True) +class CatalogInfo: # pylint: disable=R0903 + """ + Class for a CatalogInfo. + """ + + +def list_catalogs( + *, + info: Info = None, +) -> List[CatalogInfo]: + """ + List all available catalogs + """ + session = info.context["session"] # type: ignore + return [ + CatalogInfo.from_pydantic(catalog) # type: ignore #pylint: disable=E1101 + for catalog in session.exec(select(Catalog)) + ] diff --git a/datajunction-server/datajunction_server/api/graphql/engines.py b/datajunction-server/datajunction_server/api/graphql/engines.py new file mode 100644 index 000000000..3d343e5b0 --- /dev/null +++ b/datajunction-server/datajunction_server/api/graphql/engines.py @@ -0,0 +1,35 @@ +""" +Engine related APIs. +""" +from typing import List + +import strawberry +from sqlmodel import select +from strawberry.types import Info + +from datajunction_server.models.engine import Dialect as _Dialect +from datajunction_server.models.engine import Engine +from datajunction_server.models.engine import EngineInfo as _EngineInfo + +Dialect = strawberry.enum(_Dialect) + + +@strawberry.experimental.pydantic.type(model=_EngineInfo, all_fields=True) +class EngineInfo: # pylint: disable=R0903 + """ + Class for a EngineInfo. + """ + + +def list_engines( + *, + info: Info = None, +) -> List[EngineInfo]: + """ + List all available engines + """ + session = info.context["session"] # type: ignore + return [ + EngineInfo.from_pydantic(engine) # type: ignore #pylint: disable=E1101 + for engine in session.exec(select(Engine)) + ] diff --git a/datajunction-server/datajunction_server/api/graphql/main.py b/datajunction-server/datajunction_server/api/graphql/main.py new file mode 100644 index 000000000..eb668d1b2 --- /dev/null +++ b/datajunction-server/datajunction_server/api/graphql/main.py @@ -0,0 +1,37 @@ +"""DJ graphql""" + +from typing import List + +import strawberry +from fastapi import Depends +from strawberry.fastapi import GraphQLRouter + +from datajunction_server.api.graphql.catalogs import CatalogInfo, list_catalogs +from datajunction_server.api.graphql.engines import EngineInfo, list_engines +from datajunction_server.utils import get_session, get_settings + + +async def get_context(session=Depends(get_session), settings=Depends(get_settings)): + """ + provides the context for graphql requests + """ + return {"session": session, "settings": settings} + + +@strawberry.type +class Query: # pylint: disable=R0903 + """ + Parent of all DJ graphql queries + """ + + list_catalogs: List[CatalogInfo] = strawberry.field( # noqa: F811 + resolver=list_catalogs, + ) + list_engines: List[EngineInfo] = strawberry.field( # noqa: F811 + resolver=list_engines, + ) + + +schema = strawberry.Schema(query=Query) + +graphql_app = GraphQLRouter(schema, context_getter=get_context) diff --git a/datajunction-server/datajunction_server/api/health.py b/datajunction-server/datajunction_server/api/health.py new file mode 100644 index 000000000..001c6cd1d --- /dev/null +++ b/datajunction-server/datajunction_server/api/health.py @@ -0,0 +1,59 @@ +""" +Application healthchecks. +""" + +import enum +from typing import List + +from fastapi import APIRouter, Depends +from sqlalchemy import select +from sqlmodel import Session, SQLModel + +from datajunction_server.utils import get_session + +router = APIRouter(tags=["health"]) + + +class HealthcheckStatus(str, enum.Enum): + """ + Possible health statuses. + """ + + OK = "ok" + FAILED = "failed" + + +class HealthCheck(SQLModel): + """ + A healthcheck response. + """ + + name: str + status: HealthcheckStatus + + +async def database_health(session: Session) -> HealthcheckStatus: + """ + The status of the database. + """ + try: + result = session.execute(select(1)).one() + health_status = ( + HealthcheckStatus.OK if result == (1,) else HealthcheckStatus.FAILED + ) + return health_status + except Exception: # pylint: disable=broad-except + return HealthcheckStatus.FAILED + + +@router.get("/health/", response_model=List[HealthCheck]) +async def health_check(session: Session = Depends(get_session)) -> List[HealthCheck]: + """ + Healthcheck for services. + """ + return [ + HealthCheck( + name="database", + status=await database_health(session), + ), + ] diff --git a/datajunction-server/datajunction_server/api/helpers.py b/datajunction-server/datajunction_server/api/helpers.py new file mode 100644 index 000000000..5915a85bc --- /dev/null +++ b/datajunction-server/datajunction_server/api/helpers.py @@ -0,0 +1,1119 @@ +# pylint: disable=too-many-lines +""" +Helpers for API endpoints +""" +import asyncio +import collections +import http.client +import json +import logging +import time +import uuid +from datetime import datetime +from http import HTTPStatus +from typing import Dict, List, Optional, Set, Tuple, Union + +from fastapi import HTTPException +from sqlalchemy.exc import NoResultFound +from sqlalchemy.orm import joinedload +from sqlalchemy.sql.operators import is_ +from sqlmodel import Session, select + +from datajunction_server.construction.build import ( + build_materialized_cube_node, + build_metric_nodes, + build_node, +) +from datajunction_server.construction.dj_query import build_dj_query +from datajunction_server.errors import ( + DJError, + DJException, + DJInvalidInputException, + DJNodeNotFound, + ErrorCode, +) +from datajunction_server.models import AttributeType, Catalog, Column, Engine +from datajunction_server.models.attribute import RESERVED_ATTRIBUTE_NAMESPACE +from datajunction_server.models.engine import Dialect +from datajunction_server.models.history import ( + ActivityType, + EntityType, + History, + status_change_history, +) +from datajunction_server.models.metric import TranslatedSQL +from datajunction_server.models.node import ( + BuildCriteria, + MissingParent, + Node, + NodeMissingParents, + NodeMode, + NodeNamespace, + NodeRelationship, + NodeRevision, + NodeRevisionBase, + NodeStatus, + NodeType, +) +from datajunction_server.models.query import ColumnMetadata, QueryWithResults +from datajunction_server.service_clients import QueryServiceClient +from datajunction_server.sql.dag import get_nodes_with_dimension +from datajunction_server.sql.parsing import ast +from datajunction_server.sql.parsing.backends.antlr4 import SqlSyntaxError, parse +from datajunction_server.sql.parsing.backends.exceptions import DJParseException +from datajunction_server.typing import END_JOB_STATES, UTCDatetime + +_logger = logging.getLogger(__name__) + + +def get_node_namespace( # pylint: disable=too-many-arguments + session: Session, + namespace: str, + raise_if_not_exists: bool = True, +) -> NodeNamespace: + """ + Get a node namespace + """ + statement = select(NodeNamespace).where(NodeNamespace.namespace == namespace) + node_namespace = session.exec(statement).one_or_none() + if raise_if_not_exists: # pragma: no cover + if not node_namespace: + raise DJException( + message=(f"node namespace `{namespace}` does not exist."), + http_status_code=404, + ) + return node_namespace + + +def get_node_by_name( # pylint: disable=too-many-arguments + session: Session, + name: Optional[str], + node_type: Optional[NodeType] = None, + with_current: bool = False, + raise_if_not_exists: bool = True, + include_inactive: bool = False, +) -> Node: + """ + Get a node by name + """ + statement = select(Node).where(Node.name == name) + if not include_inactive: + statement = statement.where(is_(Node.deactivated_at, None)) + if node_type: + statement = statement.where(Node.type == node_type) + if with_current: + statement = statement.options(joinedload(Node.current)) + node = session.exec(statement).unique().one_or_none() + else: + node = session.exec(statement).one_or_none() + if raise_if_not_exists: + if not node: + raise DJNodeNotFound( + message=( + f"A {'' if not node_type else node_type + ' '}" + f"node with name `{name}` does not exist." + ), + http_status_code=404, + ) + return node + + +def raise_if_node_exists(session: Session, name: str) -> None: + """ + Raise an error if the node with the given name already exists. + """ + node = get_node_by_name(session, name, raise_if_not_exists=False) + if node: + raise DJException( + message=f"A node with name `{name}` already exists.", + http_status_code=HTTPStatus.CONFLICT, + ) + + +def get_column(node: NodeRevision, column_name: str) -> Column: + """ + Get a column from a node revision + """ + requested_column = None + for node_column in node.columns: + if node_column.name == column_name: + requested_column = node_column + break + + if not requested_column: + raise DJException( + message=f"Column {column_name} does not exist on node {node.name}", + http_status_code=404, + ) + return requested_column + + +def get_attribute_type( + session: Session, + name: str, + namespace: Optional[str] = RESERVED_ATTRIBUTE_NAMESPACE, +) -> Optional[AttributeType]: + """ + Gets an attribute type by name. + """ + statement = ( + select(AttributeType) + .where(AttributeType.name == name) + .where(AttributeType.namespace == namespace) + ) + return session.exec(statement).one_or_none() + + +def get_catalog_by_name(session: Session, name: str) -> Catalog: + """ + Get a catalog by name + """ + statement = select(Catalog).where(Catalog.name == name) + catalog = session.exec(statement).one_or_none() + if not catalog: + raise DJException( + message=f"Catalog with name `{name}` does not exist.", + http_status_code=404, + ) + return catalog + + +def get_query( # pylint: disable=too-many-arguments + session: Session, + node_name: str, + dimensions: List[str], + filters: List[str], + orderby: List[str], + limit: Optional[int] = None, + engine: Optional[Engine] = None, +) -> ast.Query: + """ + Get a query for a metric, dimensions, and filters + """ + node = get_node_by_name(session=session, name=node_name) + + if node.type in (NodeType.DIMENSION, NodeType.SOURCE): + if dimensions: + raise DJInvalidInputException( + message=f"Cannot set dimensions for node type {node.type}!", + ) + + # Builds the node for the engine's dialect if one is set or defaults to Spark + if ( + not engine + and node.current + and node.current.catalog + and node.current.catalog.engines + ): + engine = node.current.catalog.engines[0] + build_criteria = BuildCriteria( + dialect=(engine.dialect if engine and engine.dialect else Dialect.SPARK), + ) + + query_ast = build_node( + session=session, + node=node.current, + filters=filters, + dimensions=dimensions, + orderby=orderby, + limit=limit, + build_criteria=build_criteria, + ) + + return query_ast + + +def get_engine(session: Session, name: str, version: str) -> Engine: + """ + Return an Engine instance given an engine name and version + """ + statement = ( + select(Engine) + .where(Engine.name == name) + .where(Engine.version == (version or "")) + ) + try: + engine = session.exec(statement).one() + except NoResultFound as exc: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, + detail=f"Engine not found: `{name}` version `{version}`", + ) from exc + return engine + + +def get_downstream_nodes( + session: Session, + node_name: str, + node_type: NodeType = None, +) -> List[Node]: + """ + Gets all downstream children of the given node, filterable by node type. + Uses a recursive CTE query to build out all descendants from the node. + """ + node = get_node_by_name(session=session, name=node_name, include_inactive=True) + + dag = ( + select( + NodeRelationship.parent_id, + NodeRevision.node_id, + ) + .where(NodeRelationship.parent_id == node.id) + .join(NodeRevision, NodeRelationship.child_id == NodeRevision.id) + .join( + Node, + (Node.id == NodeRevision.node_id) + & (Node.current_version == NodeRevision.version), + ) + ).cte("dag", recursive=True) + + paths = dag.union_all( + select( + dag.c.parent_id, + NodeRevision.node_id, + ) + .join(NodeRelationship, dag.c.node_id == NodeRelationship.parent_id) + .join(NodeRevision, NodeRelationship.child_id == NodeRevision.id) + .join(Node, Node.id == NodeRevision.node_id), + ) + + statement = ( + select(Node) + .join(paths, paths.c.node_id == Node.id) + .options(joinedload(Node.current)) + ) + + results = session.exec(statement).unique().all() + + return [ + downstream + for downstream in results + if downstream.type == node_type or node_type is None + ] + + +def get_upstream_nodes( + session: Session, + node_name: str, + node_type: NodeType = None, +) -> List[Node]: + """ + Gets all upstream parents of the given node, filterable by node type. + """ + node = get_node_by_name(session=session, name=node_name) + queue = collections.deque([node]) + all_parents = set() + while queue: + node = queue.pop() + for parent in node.current.parents: + all_parents.add(parent) + for parent in node.current.parents: + queue.append(parent) + + return [ + upstream + for upstream in all_parents + if upstream.type == node_type or node_type is None + ] + + +def find_bound_dimensions( + validated_node: NodeRevision, + dependencies_map: Dict[NodeRevision, List[ast.Table]], +) -> Tuple[Set, List[Column]]: + """ + Finds the matched bound dimensions + """ + invalid_required_dimensions = set() + matched_bound_columns = [] + for col in validated_node.required_dimensions: + names = col.split(".") + parent_name, column_name = ".".join(names[:-1]), names[-1] + + found_parent_col = False + for parent in dependencies_map.keys(): + if found_parent_col: + break # pragma: no cover + if (parent.name) != parent_name: + continue # pragma: no cover + for parent_col in parent.columns: + if parent_col.name == column_name: + found_parent_col = True + matched_bound_columns.append(parent_col) + break + if not found_parent_col: + invalid_required_dimensions.add(col) + return invalid_required_dimensions, matched_bound_columns + + +def validate_node_data( # pylint: disable=too-many-locals + data: Union[NodeRevisionBase, NodeRevision], + session: Session, +) -> Tuple[ + NodeRevision, + Dict[NodeRevision, List[ast.Table]], + Dict[str, List[ast.Table]], + List[str], + List[DJError], +]: + """ + Validate a node. This function should never raise any errors. + It will build the lists of issues (including errors) and return them all + for the caller to decide what to do. + """ + + if isinstance(data, NodeRevision): + validated_node = data + else: + node = Node(name=data.name, type=data.type) + validated_node = NodeRevision.parse_obj(data) + validated_node.node = node + validated_node.status = NodeStatus.VALID + + ctx = ast.CompileContext(session=session, exception=DJException()) + + # Try to parse the node's query, extract dependencies and missing parents + # dependencies_map = missing_parents_map = {} + try: + query_ast = parse(validated_node.query) # type: ignore + dependencies_map, missing_parents_map = query_ast.extract_dependencies(ctx) + except (DJParseException, ValueError, SqlSyntaxError) as raised_exceptions: + validated_node.status = NodeStatus.INVALID + return ( + validated_node, + {}, + {}, + [], + [DJError(code=ErrorCode.INVALID_SQL_QUERY, message=str(raised_exceptions))], + ) + + # Add aliases for any unnamed columns and confirm that all column types can be inferred + query_ast.select.add_aliases_to_unnamed_columns() + + column_mapping = {col.name: col for col in validated_node.columns} + validated_node.columns = [] + type_inference_failures = {} + for col in query_ast.select.projection: + column_name = col.alias_or_name.name # type: ignore + column = column_mapping.get(column_name) + try: + column_type = str(col.type) # type: ignore + if column is None: + column = Column(name=column_name, type=column_type) + column.type = column_type # type: ignore + except DJParseException as parse_exc: + type_inference_failures[column_name] = parse_exc.message + except TypeError: + type_inference_failures[ + column_name + ] = f"Unknown TypeError on column {column_name}." + if column: + validated_node.columns.append(column) + + # check that bound dimensions are from parent nodes + invalid_required_dimensions, matched_bound_columns = find_bound_dimensions( + validated_node, + dependencies_map, + ) + validated_node.required_dimensions = matched_bound_columns + + errors = [] + if missing_parents_map or type_inference_failures or invalid_required_dimensions: + # update status (if needed) + if validated_node.mode == NodeMode.DRAFT: + validated_node.status = NodeStatus.INVALID + # build errors + missing_parents_error = ( + [ + DJError( + code=ErrorCode.MISSING_PARENT, + message="Node definition contains references to nodes that do not exist", + debug={"missing_parents": list(missing_parents_map.keys())}, + ), + ] + if missing_parents_map + else [] + ) + type_inference_error = ( + [ + DJError( + code=ErrorCode.TYPE_INFERENCE, + message=( + f"Unable to infer type for some columns on node `{data.name}`.\n" + + ("\n\t* " if type_inference_failures else "") + + "\n\t* ".join( + [val[:103] for val in type_inference_failures.values()], + ) + ), + debug={ + "columns": type_inference_failures, + "errors": ctx.exception.errors, + }, + ), + ] + if type_inference_failures + else [] + ) + invalid_required_dimensions_error = ( + [ + DJError( + code=ErrorCode.INVALID_COLUMN, + message=( + "Node definition contains references to columns as " + "required dimensions that are not on parent nodes." + ), + debug={ + "invalid_required_dimensions": list( + invalid_required_dimensions, + ), + }, + ), + ] + if invalid_required_dimensions + else [] + ) + errors = ( + missing_parents_error + + type_inference_error + + invalid_required_dimensions_error + ) + + return ( + validated_node, + dependencies_map, + missing_parents_map, + list(type_inference_failures.keys()), + errors, + ) + + +def resolve_downstream_references( + session: Session, + node_revision: NodeRevision, +) -> List[NodeRevision]: + """ + Find all node revisions with missing parent references to `node` and resolve them + """ + missing_parents = session.exec( + select(MissingParent).where(MissingParent.name == node_revision.name), + ).all() + newly_valid_nodes = [] + for missing_parent in missing_parents: + missing_parent_links = session.exec( + select(NodeMissingParents).where( + NodeMissingParents.missing_parent_id == missing_parent.id, + ), + ).all() + for ( + link + ) in missing_parent_links: # Remove from missing parents and add to parents + downstream_node_id = link.referencing_node_id + downstream_node_revision = ( + session.exec( + select(NodeRevision).where(NodeRevision.id == downstream_node_id), + ) + .unique() + .one() + ) + downstream_node_revision.parents.append(node_revision.node) + downstream_node_revision.missing_parents.remove(missing_parent) + (_, _, _, _, errors) = validate_node_data( + data=downstream_node_revision, + session=session, + ) + if not errors: + newly_valid_nodes.append(downstream_node_revision) + session.add(downstream_node_revision) + session.commit() + + session.delete(missing_parent) # Remove missing parent reference to node + return newly_valid_nodes + + +def propagate_valid_status( + session: Session, + valid_nodes: List[NodeRevision], + catalog_id: int, +) -> None: + """ + Propagate a valid status by revalidating all downstream nodes + """ + while valid_nodes: + resolved_nodes = [] + for node_revision in valid_nodes: + if node_revision.status != NodeStatus.VALID: + raise DJException( + f"Cannot propagate valid status: Node `{node_revision.name}` is not valid", + ) + downstream_nodes = get_downstream_nodes( + session=session, + node_name=node_revision.name, + ) + newly_valid_nodes = [] + for node in downstream_nodes: + (validated_node, _, _, _, errors) = validate_node_data( + data=node.current, + session=session, + ) + if not errors: + node.current.columns = validated_node.columns or [] + node.current.status = NodeStatus.VALID + node.current.catalog_id = catalog_id + session.add( + status_change_history( + node.current, + NodeStatus.INVALID, + NodeStatus.VALID, + ), + ) + session.add(node.current) + session.commit() + newly_valid_nodes.append(node.current) + resolved_nodes.extend(newly_valid_nodes) + valid_nodes = resolved_nodes + + +def validate_cube( # pylint: disable=too-many-locals + session: Session, + metric_names: List[str], + dimension_names: List[str], +) -> Tuple[List[Column], List[Node], List[Node], List[Column], Optional[Catalog]]: + """ + Validate that a set of metrics and dimensions can be built together. + """ + metrics: List[Column] = [] + metric_nodes: List[Node] = [] + dimension_nodes: List[Node] = [] + dimensions: List[Column] = [] + catalogs = [] + catalog = None + + # Verify that the provided metrics are metric nodes + for node_name in metric_names: + metric_node = get_node_by_name(session=session, name=node_name) + if metric_node.type != NodeType.METRIC: + raise DJException( + message=( + f"Node {metric_node.name} of type {metric_node.type} " + f"cannot be added to a cube." + + " Did you mean to add a dimension attribute?" + if metric_node.type == NodeType.DIMENSION + else "" + ), + http_status_code=http.client.UNPROCESSABLE_ENTITY, + ) + catalogs.append(metric_node.current.catalog.name) + catalog = metric_node.current.catalog + metrics.append(metric_node.current.columns[0]) + metric_nodes.append(metric_node) + + if not metrics: + raise DJException( + message=("At least one metric is required"), + http_status_code=http.client.UNPROCESSABLE_ENTITY, + ) + + # Verify that the provided dimension attributes exist + for dimension_attribute in dimension_names: + node_name, column_name = dimension_attribute.rsplit(".", 1) + try: + dimension_node = get_node_by_name(session=session, name=node_name) + except DJNodeNotFound as exc: # pragma: no cover + raise DJException( + f"{exc.message} Please make sure that `{column_name}` " + f"is an attribute on a dimension node `{node_name}`.", + ) from exc + dimension_nodes.append(dimension_node) + columns = {col.name: col for col in dimension_node.current.columns} + if column_name in columns: # pragma: no cover + dimensions.append(columns[column_name]) + + if not dimensions: + raise DJException( + message=("At least one dimension is required"), + http_status_code=http.client.UNPROCESSABLE_ENTITY, + ) + + if len(set(catalogs)) > 1: + raise DJException( + message=( + f"Metrics and dimensions cannot be from multiple catalogs: {catalogs}" + ), + ) + + if len(set(catalogs)) < 1: # pragma: no cover + raise DJException( + message=("Metrics and dimensions must be part of a common catalog"), + ) + + return metrics, metric_nodes, dimension_nodes, dimensions, catalog + + +def get_history( + session: Session, + entity_type: EntityType, + entity_name: str, + offset: int, + limit: int, +): + """ + Get the history for a given entity type and name + """ + return session.exec( + select(History) + .where(History.entity_type == entity_type) + .where(History.entity_name == entity_name) + .offset(offset) + .limit(limit), + ).all() + + +def validate_orderby( + orderby: List[str], + metrics: List[str], + dimension_attributes: List[str], +): + """ + Validate that all elements in an order by match a metric or dimension attribute + """ + invalid_orderbys = [] + for orderby_element in orderby: + if orderby_element not in metrics + dimension_attributes: + invalid_orderbys.append(orderby_element) + if invalid_orderbys: + raise DJException( + message=( + f"Columns {invalid_orderbys} in order by clause must also be " + "specified in the metrics or dimensions" + ), + ) + + +def find_existing_cube( + session: Session, + metric_columns: List[Column], + dimension_columns: List[Column], + materialized: bool = True, +) -> Optional[NodeRevision]: + """ + Find an existing cube with these metrics and dimensions, if any. + If `materialized` is set, it will only look for materialized cubes. + """ + element_names = [col.name for col in (metric_columns + dimension_columns)] + statement = select(NodeRevision) + for name in element_names: + statement = statement.filter( + NodeRevision.cube_elements.any(Column.name == name), # type: ignore # pylint: disable=no-member + ) + + existing_cubes = session.exec(statement).unique().all() + for cube in existing_cubes: + if not materialized or ( # pragma: no cover + materialized and cube.materializations and cube.availability + ): + return cube + return None + + +def build_sql_for_multiple_metrics( # pylint: disable=too-many-arguments,too-many-locals + session: Session, + metrics: List[str], + dimensions: List[str], + filters: List[str] = None, + orderby: List[str] = None, + limit: Optional[int] = None, + engine_name: Optional[str] = None, + engine_version: Optional[str] = None, +) -> Tuple[TranslatedSQL, Engine, Catalog]: + """ + Build SQL for multiple metrics. Used by both /sql and /data endpoints + """ + if not filters: + filters = [] + if not orderby: + orderby = [] + + metric_columns, metric_nodes, _, dimension_columns, _ = validate_cube( + session, + metrics, + dimensions, + ) + leading_metric_node = get_node_by_name(session, metrics[0]) + available_engines = leading_metric_node.current.catalog.engines + + # Try to find a built cube that already has the given metrics and dimensions + # The cube needs to have a materialization configured and an availability state + # posted in order for us to use the materialized datasource + cube = find_existing_cube( + session, + metric_columns, + dimension_columns, + materialized=True, + ) + if cube: + catalog = get_catalog_by_name(session, cube.availability.catalog) # type: ignore + available_engines = catalog.engines + available_engines + + # Check if selected engine is available + engine = ( + get_engine(session, engine_name, engine_version) # type: ignore + if engine_name + else available_engines[0] + ) + if engine not in available_engines: + raise DJInvalidInputException( # pragma: no cover + f"The selected engine is not available for the node {metrics[0]}. " + f"Available engines include: {', '.join(engine.name for engine in available_engines)}", + ) + + validate_orderby(orderby, metrics, dimensions) + + if cube and cube.materializations and cube.availability: + query_ast = build_materialized_cube_node( + metric_columns, + dimension_columns, + cube, + ) + return ( + TranslatedSQL( + sql=str(query_ast), + columns=[ + ColumnMetadata(name=col.name, type=str(col.type)) + for col in (metric_columns + dimension_columns) + ], + dialect=engine.dialect, + ), + engine, + cube.catalog, + ) + + query_ast = build_metric_nodes( + session, + metric_nodes, + filters=filters or [], + dimensions=dimensions or [], + orderby=orderby or [], + limit=limit, + ) + columns = [ + ColumnMetadata(name=col.alias_or_name.name, type=str(col.type)) # type: ignore + for col in query_ast.select.projection + ] + return ( + TranslatedSQL( + sql=str(query_ast), + columns=columns, + dialect=engine.dialect if engine else None, + ), + engine, + leading_metric_node.current.catalog, + ) + + +async def query_event_stream( # pylint: disable=too-many-arguments + query: QueryWithResults, + query_service_client: QueryServiceClient, + columns: List[Column], + request, + timeout: float = 0.0, + stream_delay: float = 0.5, + retry_timeout: int = 5000, +): + """ + A generator of events from a query submitted to the query service + """ + starting_time = time.time() + # Start with query and query_next as the initial state of the query + query_prev = query_next = query + query_id = query_prev.id + _logger.info("sending initial event to the client for query %s", query_id) + yield { + "event": "message", + "id": uuid.uuid4(), + "retry": retry_timeout, + "data": json.dumps(query.json()), + } + # Continuously check the query until it's complete + while not timeout or (time.time() - starting_time < timeout): + # Check if the client closed the connection + if await request.is_disconnected(): # pragma: no cover + _logger.error("connection closed by the client") + break + + # Check the current state of the query + query_next = query_service_client.get_query(query_id=query_id) # type: ignore + if query_next.state in END_JOB_STATES: + _logger.info( + "query end state detected (%s), sending final event to the client", + query_next.state, + ) + if query_next.results.__root__: # pragma: no cover + query_next.results.__root__[0].columns = columns or [] + yield { + "event": "message", + "id": uuid.uuid4(), + "retry": retry_timeout, + "data": json.dumps(query_next.json()), + } + _logger.info("connection closed by the server") + break + if query_prev != query_next: # pragma: no cover + _logger.info( + "query information has changed, sending an event to the client", + ) + yield { + "event": "message", + "id": uuid.uuid4(), + "retry": retry_timeout, + "data": json.dumps(query_next.json()), + } + + query = query_next + await asyncio.sleep(stream_delay) # pragma: no cover + + +def build_sql_for_dj_query( # pylint: disable=too-many-arguments,too-many-locals + session: Session, + query: str, + engine_name: Optional[str] = None, + engine_version: Optional[str] = None, +) -> Tuple[TranslatedSQL, Engine, Catalog]: + """ + Build SQL for multiple metrics. Used by /djsql endpoints + """ + + query_ast, metrics = build_dj_query(session, query) + + leading_metric_node = metrics[0] + available_engines = leading_metric_node.current.catalog.engines + + # Check if selected engine is available + engine = ( + get_engine(session, engine_name, engine_version) # type: ignore + if engine_name + else available_engines[0] + ) + + if engine not in available_engines: + raise DJInvalidInputException( # pragma: no cover + f"The selected engine is not available for the node {leading_metric_node.name}. " + f"Available engines include: {', '.join(engine.name for engine in available_engines)}", + ) + + columns = [ + ColumnMetadata(name=col.alias_or_name.name, type=str(col.type)) # type: ignore + for col in query_ast.select.projection + ] + return ( + TranslatedSQL( + sql=str(query_ast), + columns=columns, + dialect=engine.dialect if engine else None, + ), + engine, + leading_metric_node.current.catalog, + ) + + +def deactivate_node(session: Session, name: str, message: str = None): + """ + Deactivates a node and propagates to all downstreams. + """ + node = get_node_by_name(session, name, with_current=True) + + # Find all downstream nodes and mark them as invalid + downstreams = get_downstream_nodes(session, node.name) + for downstream in downstreams: + if downstream.current.status != NodeStatus.INVALID: + downstream.current.status = NodeStatus.INVALID + session.add( + status_change_history( + downstream.current, + NodeStatus.VALID, + NodeStatus.INVALID, + parent_node=node.name, + ), + ) + session.add(downstream) + + now = datetime.utcnow() + node.deactivated_at = UTCDatetime( + year=now.year, + month=now.month, + day=now.day, + hour=now.hour, + minute=now.minute, + second=now.second, + ) + session.add(node) + session.add( + History( + entity_type=EntityType.NODE, + entity_name=node.name, + node=node.name, + activity_type=ActivityType.DELETE, + details={"message": message} if message else {}, + ), + ) + session.commit() + + +def activate_node(session: Session, name: str, message: str = None): + """Restores node and revalidate all downstreams.""" + node = get_node_by_name(session, name, with_current=True, include_inactive=True) + if not node.deactivated_at: + raise DJException( + http_status_code=HTTPStatus.BAD_REQUEST, + message=f"Cannot restore `{name}`, node already active.", + ) + node.deactivated_at = None # type: ignore + + # Find all downstream nodes and revalidate them + downstreams = get_downstream_nodes(session, node.name) + for downstream in downstreams: + old_status = downstream.current.status + if downstream.type == NodeType.CUBE: + downstream.current.status = NodeStatus.VALID + for element in downstream.current.cube_elements: + if ( + element.node_revisions + and element.node_revisions[-1].status == NodeStatus.INVALID + ): # pragma: no cover + downstream.current.status = NodeStatus.INVALID + else: + # We should not fail node restoration just because of some nodes + # that have been invalid already and stay that way. + (_, _, _, _, errors) = validate_node_data(downstream.current, session) + if errors: + downstream.current.status = NodeStatus.INVALID + session.add(downstream) + if old_status != downstream.current.status: + session.add( + status_change_history( + downstream.current, + old_status, + downstream.current.status, + parent_node=node.name, + ), + ) + + session.add(node) + session.add( + History( + entity_type=EntityType.NODE, + entity_name=node.name, + node=node.name, + activity_type=ActivityType.RESTORE, + details={"message": message} if message else {}, + ), + ) + session.commit() + + +def revalidate_node(name: str, session: Session, parent_node: str = None): + """ + Revalidate a single existing node and update its status appropriately + """ + node = get_node_by_name(session, name) + current_node_revision = node.current + if current_node_revision.type == NodeType.SOURCE: + if current_node_revision.status != NodeStatus.VALID: # pragma: no cover + current_node_revision.status = NodeStatus.VALID + session.add( + status_change_history( + current_node_revision, + NodeStatus.INVALID, + NodeStatus.VALID, + ), + ) + session.add(current_node_revision) + session.commit() + return NodeStatus.VALID + previous_status = current_node_revision.status + (_, _, _, _, errors) = validate_node_data(current_node_revision, session) + if errors: + status = NodeStatus.INVALID # pragma: no cover + else: + status = NodeStatus.VALID + + if previous_status != status: # pragma: no cover + current_node_revision.status = status + session.add(current_node_revision) + session.add( + status_change_history( + current_node_revision, + previous_status, + current_node_revision.status, + parent_node=parent_node, + ), + ) + session.commit() + return status + + +def hard_delete_node(name: str, session: Session): + """ + Hard delete a node, destroying all links and invalidating all downstream nodes. + This should be used with caution, deactivating a node is preferred. + """ + node = get_node_by_name(session, name, with_current=True) + downstream_nodes = get_downstream_nodes(session=session, node_name=name) + + linked_nodes = [] + if node.type == NodeType.DIMENSION: + linked_nodes = get_nodes_with_dimension(session=session, dimension_node=node) + + session.delete(node) + session.commit() + impact = [] # Aggregate all impact of this deletion to include in response + + # Revalidate all downstream nodes + for node in downstream_nodes: + session.add( # Capture this in the downstream node's history + History( + entity_type=EntityType.DEPENDENCY, + entity_name=name, + node=node.name, + activity_type=ActivityType.DELETE, + ), + ) + status = revalidate_node(name=node.name, session=session, parent_node=name) + impact.append( + { + "name": node.name, + "status": status, + "effect": "downstream node is now invalid", + }, + ) + + # Revalidate all linked nodes + for node in linked_nodes: + session.add( # Capture this in the downstream node's history + History( + entity_type=EntityType.LINK, + entity_name=name, + node=node.name, + activity_type=ActivityType.DELETE, + ), + ) + status = revalidate_node(name=node.name, session=session) + impact.append( + { + "name": node.name, + "status": status, + "effect": "broken link", + }, + ) + session.add( # Capture this in the downstream node's history + History( + entity_type=EntityType.NODE, + entity_name=name, + node=name, + activity_type=ActivityType.DELETE, + details={ + "impact": impact, + }, + ), + ) + session.commit() # Commit the history events + return impact diff --git a/datajunction-server/datajunction_server/api/history.py b/datajunction-server/datajunction_server/api/history.py new file mode 100644 index 000000000..eceb3d391 --- /dev/null +++ b/datajunction-server/datajunction_server/api/history.py @@ -0,0 +1,55 @@ +""" +History related APIs. +""" + +import logging +from typing import List + +from fastapi import APIRouter, Depends, Query +from sqlmodel import Session, select + +from datajunction_server.api.helpers import get_history +from datajunction_server.models.history import EntityType, History +from datajunction_server.utils import get_session + +_logger = logging.getLogger(__name__) +router = APIRouter(tags=["history"]) + + +@router.get("/history/{entity_type}/{entity_name}/", response_model=List[History]) +def list_history( + entity_type: EntityType, + entity_name: str, + offset: int = 0, + limit: int = Query(default=100, lte=100), + *, + session: Session = Depends(get_session), +) -> List[History]: + """ + List history for an entity type (i.e. Node) and entity name + """ + hist = get_history( + session=session, + entity_name=entity_name, + entity_type=entity_type, + offset=offset, + limit=limit, + ) + return hist + + +@router.get("/history/", response_model=List[History]) +def list_history_by_node_context( + node: str, + offset: int = 0, + limit: int = Query(default=100, lte=100), + *, + session: Session = Depends(get_session), +) -> List[History]: + """ + List all activity history for a node context + """ + hist = session.exec( + select(History).where(History.node == node).offset(offset).limit(limit), + ).all() + return hist diff --git a/datajunction-server/datajunction_server/api/logging.conf b/datajunction-server/datajunction_server/api/logging.conf new file mode 100644 index 000000000..867f5ed84 --- /dev/null +++ b/datajunction-server/datajunction_server/api/logging.conf @@ -0,0 +1,30 @@ +[loggers] +keys=root + +[handlers] +keys=consoleHandler,detailedConsoleHandler + +[formatters] +keys=normalFormatter,detailedFormatter + +[logger_root] +level=INFO +handlers=consoleHandler + +[handler_consoleHandler] +class=StreamHandler +level=DEBUG +formatter=normalFormatter +args=(sys.stdout,) + +[handler_detailedConsoleHandler] +class=StreamHandler +level=DEBUG +formatter=detailedFormatter +args=(sys.stdout,) + +[formatter_normalFormatter] +format=[%(asctime)s] %(levelname)s: %(name)s: %(message)s + +[formatter_detailedFormatter] +format=%(asctime)s loglevel=%(levelname)-6s logger=%(name)s %(funcName)s() L%(lineno)-4d %(message)s call_trace=%(pathname)s L%(lineno)-4d diff --git a/datajunction-server/datajunction_server/api/main.py b/datajunction-server/datajunction_server/api/main.py new file mode 100644 index 000000000..c249647cf --- /dev/null +++ b/datajunction-server/datajunction_server/api/main.py @@ -0,0 +1,159 @@ +""" +Main DJ server app. +""" + +# All the models need to be imported here so that SQLModel can define their +# relationships at runtime without causing circular imports. +# See https://sqlmodel.tiangolo.com/tutorial/code-structure/#make-circular-imports-work. +# pylint: disable=unused-import,ungrouped-imports + +import logging +from logging import config +from os import path +from typing import TYPE_CHECKING + +from fastapi import Depends, FastAPI, Request +from fastapi.responses import JSONResponse, RedirectResponse, Response +from jose import JWTError +from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor +from sqlmodel import select +from starlette.middleware.cors import CORSMiddleware + +from datajunction_server import __version__ +from datajunction_server.api import ( + attributes, + catalogs, + client, + cubes, + data, + dimensions, + djsql, + engines, + health, + history, + materializations, + metrics, + namespaces, + nodes, + sql, + tags, +) +from datajunction_server.api.attributes import default_attribute_types +from datajunction_server.api.graphql.main import graphql_app +from datajunction_server.errors import DJError, DJException +from datajunction_server.internal.authentication.basic import parse_basic_auth_cookie +from datajunction_server.internal.authentication.github import parse_github_auth_cookie +from datajunction_server.models.catalog import Catalog +from datajunction_server.models.column import Column +from datajunction_server.models.engine import Engine +from datajunction_server.models.node import NodeRevision +from datajunction_server.models.table import Table +from datajunction_server.models.user import User +from datajunction_server.utils import get_settings + +if TYPE_CHECKING: # pragma: no cover + from opentelemetry import trace + +_logger = logging.getLogger(__name__) +settings = get_settings() + +UNAUTHENTICATED_ENDPOINTS = [ + "/docs", + "/openapi.json", + "/basic/user/", + "/basic/login/", + "/github/login/", + "/github/token/", +] +BASIC_OAUTH_CONFIGURED = settings.secret or False + + +config.fileConfig( + path.join(path.dirname(path.abspath(__file__)), "logging.conf"), + disable_existing_loggers=False, +) + +dependencies = [Depends(default_attribute_types)] + +# Only inject basic auth middleware if a server secret is configured +if settings.secret: # pragma: no cover + dependencies.append(Depends(parse_basic_auth_cookie)) +if all( + [ + settings.secret, + settings.github_oauth_client_id, + settings.github_oauth_client_secret, + ], +): # pragma: no cover + dependencies.append(Depends(parse_github_auth_cookie)) + +app = FastAPI( + title=settings.name, + description=settings.description, + version=__version__, + license_info={ + "name": "MIT License", + "url": "https://mit-license.org/", + }, + dependencies=dependencies, +) +app.add_middleware( + CORSMiddleware, + allow_origins=settings.cors_origin_whitelist, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +app.include_router(catalogs.router) +app.include_router(engines.router) +app.include_router(metrics.router) +app.include_router(djsql.router) +app.include_router(nodes.router) +app.include_router(namespaces.router) +app.include_router(materializations.router) +app.include_router(data.router) +app.include_router(health.router) +app.include_router(history.router) +app.include_router(cubes.router) +app.include_router(tags.router) +app.include_router(attributes.router) +app.include_router(sql.router) +app.include_router(client.router) +app.include_router(dimensions.router) +app.include_router(graphql_app, prefix="/graphql") + + +@app.exception_handler(DJException) +async def dj_exception_handler( # pylint: disable=unused-argument + request: Request, + exc: DJException, +) -> JSONResponse: + """ + Capture errors and return JSON. + """ + _logger.exception(exc) + return JSONResponse( + status_code=exc.http_status_code, + content=exc.to_dict(), + headers={"X-DJ-Error": "true", "X-DBAPI-Exception": exc.dbapi_exception}, + ) + + +# Only mount basic auth router if a server secret is configured +if settings.secret: # pragma: no cover + from datajunction_server.api.authentication import basic + + app.include_router(basic.router) + +# Only mount github auth router if a github client id and secret are configured +if all( + [ + settings.secret, + settings.github_oauth_client_id, + settings.github_oauth_client_secret, + ], +): # pragma: no cover + from datajunction_server.api.authentication import github + + app.include_router(github.router) diff --git a/datajunction-server/datajunction_server/api/materializations.py b/datajunction-server/datajunction_server/api/materializations.py new file mode 100644 index 000000000..84cf8edd4 --- /dev/null +++ b/datajunction-server/datajunction_server/api/materializations.py @@ -0,0 +1,240 @@ +# pylint: disable=too-many-lines +""" +Node materialization related APIs. +""" +import logging +from datetime import datetime +from http import HTTPStatus +from typing import List + +from fastapi import APIRouter, Depends +from fastapi.responses import JSONResponse +from sqlmodel import Session + +from datajunction_server.api.helpers import get_node_by_name +from datajunction_server.errors import DJException +from datajunction_server.internal.materializations import ( + create_new_materialization, + schedule_materialization_jobs, +) +from datajunction_server.models.history import ActivityType, EntityType, History +from datajunction_server.models.materialization import ( + MaterializationConfigInfoUnified, + UpsertMaterialization, +) +from datajunction_server.models.node import NodeType +from datajunction_server.service_clients import QueryServiceClient +from datajunction_server.typing import UTCDatetime +from datajunction_server.utils import get_query_service_client, get_session + +_logger = logging.getLogger(__name__) +router = APIRouter(tags=["materializations"]) + + +@router.post( + "/nodes/{name}/materialization/", + status_code=201, + name="Insert or Update a Materialization for a Node", +) +def upsert_materialization( # pylint: disable=too-many-locals + name: str, + data: UpsertMaterialization, + *, + session: Session = Depends(get_session), + query_service_client: QueryServiceClient = Depends(get_query_service_client), +) -> JSONResponse: + """ + Add or update a materialization of the specified node. If a name is specified + for the materialization config, it will always update that named config. + """ + node = get_node_by_name(session, name, with_current=True) + if node.type == NodeType.SOURCE: + raise DJException( + http_status_code=HTTPStatus.BAD_REQUEST, + message=f"Cannot set materialization config for source node `{name}`!", + ) + current_revision = node.current + old_materializations = {mat.name: mat for mat in current_revision.materializations} + + # Create a new materialization + new_materialization = create_new_materialization(session, current_revision, data) + + # Check to see if a materialization for this engine already exists with the exact same config + existing_materialization = old_materializations.get(new_materialization.name) + deactivated_before = False + if ( + existing_materialization + and existing_materialization.config == new_materialization.config + ): + new_materialization.node_revision = None # type: ignore + # if the materialization was deactivated before, restore it + if existing_materialization.deactivated_at is not None: + deactivated_before = True + existing_materialization.deactivated_at = None # type: ignore + session.add( + History( + entity_type=EntityType.MATERIALIZATION, + entity_name=existing_materialization.name, + node=node.name, + activity_type=ActivityType.RESTORE, + details={}, + ), + ) + session.commit() + session.refresh(existing_materialization) + existing_materialization_info = query_service_client.get_materialization_info( + name, + current_revision.version, # type: ignore + new_materialization.name, # type: ignore + ) + return JSONResponse( + status_code=HTTPStatus.CREATED, + content={ + "message": ( + f"The same materialization config with name `{new_materialization.name}` " + f"already exists for node `{name}` so no update was performed." + if not deactivated_before + else f"The same materialization config with name `{new_materialization.name}` " + f"already exists for node `{name}` but was deactivated. It has now been " + f"restored." + ), + "info": existing_materialization_info.dict(), + }, + ) + # If changes are detected, save the new materialization + existing_materialization_names = { + mat.name for mat in current_revision.materializations + } + unchanged_existing_materializations = [ + config + for config in current_revision.materializations + if config.name != new_materialization.name + ] + current_revision.materializations = unchanged_existing_materializations + [ # type: ignore + new_materialization, + ] + + # This will add the materialization config, the new node rev, and update the node's version. + session.add(current_revision) + session.add(node) + + session.add( + History( + entity_type=EntityType.MATERIALIZATION, + node=node.name, + entity_name=new_materialization.name, + activity_type=( + ActivityType.CREATE + if new_materialization.name in existing_materialization_names + else ActivityType.UPDATE + ), + details={ + "node": node.name, + "materialization": new_materialization.name, + }, + ), + ) + session.commit() + + materialization_response = schedule_materialization_jobs( + [new_materialization], + query_service_client, + ) + return JSONResponse( + status_code=200, + content={ + "message": ( + f"Successfully updated materialization config named `{new_materialization.name}` " + f"for node `{name}`" + ), + "urls": [output.urls for output in materialization_response.values()], + }, + ) + + +@router.get( + "/nodes/{node_name}/materializations/", + response_model=List[MaterializationConfigInfoUnified], + name="List Materializations for a Node", +) +def list_node_materializations( + node_name: str, + show_deleted: bool = False, + *, + session: Session = Depends(get_session), + query_service_client: QueryServiceClient = Depends(get_query_service_client), +) -> List[MaterializationConfigInfoUnified]: + """ + Show all materializations configured for the node, with any associated metadata + like urls from the materialization service, if available. + """ + node = get_node_by_name(session, node_name, with_current=True) + materializations = [] + for materialization in node.current.materializations: + if not materialization.deactivated_at or show_deleted: # pragma: no cover + info = query_service_client.get_materialization_info( + node_name, + node.current.version, # type: ignore + materialization.name, # type: ignore + ) + materialization = MaterializationConfigInfoUnified( + **materialization.dict(), + **{"engine": materialization.engine.dict()}, + **info.dict(), + ) + materializations.append(materialization) + return materializations + + +@router.delete( + "/nodes/{node_name}/materializations/", + response_model=List[MaterializationConfigInfoUnified], + name="Deactivate a Materialization for a Node", +) +def deactivate_node_materializations( + node_name: str, + materialization_name: str, + *, + session: Session = Depends(get_session), + query_service_client: QueryServiceClient = Depends(get_query_service_client), +) -> List[MaterializationConfigInfoUnified]: + """ + Deactivate the node materialization with the provided name. + Also calls the query service to deactivate the associated scheduled jobs. + """ + node = get_node_by_name(session, node_name, with_current=True) + query_service_client.deactivate_materialization(node_name, materialization_name) + for materialization in node.current.materializations: + if ( + materialization.name == materialization_name + and not materialization.deactivated_at + ): # pragma: no cover + now = datetime.utcnow() + materialization.deactivated_at = UTCDatetime( + year=now.year, + month=now.month, + day=now.day, + hour=now.hour, + minute=now.minute, + second=now.second, + ) + session.add(materialization) + + session.add( + History( + entity_type=EntityType.MATERIALIZATION, + entity_name=materialization_name, + node=node.name, + activity_type=ActivityType.DELETE, + details={}, + ), + ) + session.commit() + session.refresh(node.current) + return JSONResponse( + status_code=HTTPStatus.OK, + content={ + "message": f"The materialization named `{materialization_name}` on node `{node_name}` " + "has been successfully deactivated", + }, + ) diff --git a/datajunction-server/datajunction_server/api/metrics.py b/datajunction-server/datajunction_server/api/metrics.py new file mode 100644 index 000000000..bd1169906 --- /dev/null +++ b/datajunction-server/datajunction_server/api/metrics.py @@ -0,0 +1,99 @@ +""" +Metric related APIs. +""" + +from http import HTTPStatus +from typing import List + +from fastapi import APIRouter, Depends, HTTPException, Query +from sqlalchemy.exc import NoResultFound +from sqlalchemy.sql.operators import is_ +from sqlmodel import Session, select + +from datajunction_server.api.helpers import get_node_by_name +from datajunction_server.errors import DJError, DJException, ErrorCode +from datajunction_server.models.metric import Metric +from datajunction_server.models.node import DimensionAttributeOutput, Node, NodeType +from datajunction_server.sql.dag import get_shared_dimensions +from datajunction_server.utils import get_session + +router = APIRouter(tags=["metrics"]) + + +def get_metric(session: Session, name: str) -> Node: + """ + Return a metric node given a node name. + """ + node = get_node_by_name(session, name) + if node.type != NodeType.METRIC: + raise HTTPException( + status_code=HTTPStatus.BAD_REQUEST, + detail=f"Not a metric node: `{name}`", + ) + return node + + +@router.get("/metrics/", response_model=List[str]) +def list_metrics(*, session: Session = Depends(get_session)) -> List[str]: + """ + List all available metrics. + """ + return session.exec( + select(Node.name) + .where(Node.type == NodeType.METRIC) + .where(is_(Node.deactivated_at, None)), + ).all() + + +@router.get("/metrics/{name}/", response_model=Metric) +def get_a_metric(name: str, *, session: Session = Depends(get_session)) -> Metric: + """ + Return a metric by name. + """ + node = get_metric(session, name) + return Metric.parse_node(node) + + +@router.get( + "/metrics/common/dimensions/", + response_model=List[DimensionAttributeOutput], +) +async def get_common_dimensions( + metric: List[str] = Query( + title="List of metrics to find common dimensions for", + default=[], + ), + session: Session = Depends(get_session), +) -> List[DimensionAttributeOutput]: + """ + Return common dimensions for a set of metrics. + """ + metric_nodes = [] + errors = [] + for node_name in metric: + statement = ( + select(Node) + .where(Node.name == node_name) + .where(is_(Node.deactivated_at, None)) + ) + try: + node = session.exec(statement).one() + if node.type != NodeType.METRIC: + errors.append( + DJError( + message=f"Not a metric node: {node_name}", + code=ErrorCode.NODE_TYPE_ERROR, + ), + ) + metric_nodes.append(node) + except NoResultFound: + errors.append( + DJError( + message=f"Metric node not found: {node_name}", + code=ErrorCode.UNKNOWN_NODE, + ), + ) + + if errors: + raise DJException(errors=errors) + return get_shared_dimensions(metric_nodes) diff --git a/datajunction-server/datajunction_server/api/namespaces.py b/datajunction-server/datajunction_server/api/namespaces.py new file mode 100644 index 000000000..75d17f21e --- /dev/null +++ b/datajunction-server/datajunction_server/api/namespaces.py @@ -0,0 +1,212 @@ +""" +Node namespace related APIs. +""" +import logging +from http import HTTPStatus +from typing import List, Optional + +from fastapi import APIRouter, Depends, Query +from fastapi.responses import JSONResponse +from sqlalchemy.sql.operators import is_ +from sqlmodel import Session, select + +from datajunction_server.api.helpers import ( + activate_node, + deactivate_node, + get_node_namespace, +) +from datajunction_server.errors import DJAlreadyExistsException +from datajunction_server.internal.namespaces import ( + create_namespace, + get_nodes_in_namespace, + mark_namespace_deactivated, + mark_namespace_restored, +) +from datajunction_server.models.node import NodeNameList, NodeNamespace, NodeType +from datajunction_server.utils import get_session + +_logger = logging.getLogger(__name__) +router = APIRouter(tags=["namespaces"]) + + +@router.post("/namespaces/{namespace}/", status_code=HTTPStatus.CREATED) +def create_node_namespace( + namespace: str, + session: Session = Depends(get_session), +) -> JSONResponse: + """ + Create a node namespace + """ + if get_node_namespace( + session=session, + namespace=namespace, + raise_if_not_exists=False, + ): # pragma: no cover + return JSONResponse( + status_code=409, + content={ + "message": (f"Node namespace `{namespace}` already exists"), + }, + ) + create_namespace(session, namespace) + return JSONResponse( + status_code=HTTPStatus.CREATED, + content={ + "message": (f"Node namespace `{namespace}` has been successfully created"), + }, + ) + + +@router.get( + "/namespaces/", + response_model=List[str], + status_code=200, +) +def list_namespaces( + session: Session = Depends(get_session), +) -> List[str]: + """ + List namespaces + """ + return session.exec( + select(NodeNamespace.namespace).where(is_(NodeNamespace.deactivated_at, None)), + ).all() + + +@router.get( + "/namespaces/{namespace}/", + response_model=NodeNameList, + status_code=200, +) +def list_nodes_in_namespace( + namespace: str, + type_: Optional[NodeType] = Query( + default=None, + description="Filter the list of nodes to this type", + ), + session: Session = Depends(get_session), +) -> NodeNameList: + """ + List node names in namespace, filterable to a given type if desired. + """ + return get_nodes_in_namespace(session, namespace, type_) # type: ignore + + +@router.delete("/namespaces/{namespace}/", status_code=HTTPStatus.OK) +def deactivate_a_namespace( + namespace: str, + cascade: bool = Query( + default=False, + description="Cascade the deletion down to the nodes in the namespace", + ), + session: Session = Depends(get_session), +) -> JSONResponse: + """ + Deactivates a node namespace + """ + node_namespace = get_node_namespace( + session=session, + namespace=namespace, + raise_if_not_exists=True, + ) + + if node_namespace.deactivated_at: + raise DJAlreadyExistsException( + message=f"Namespace `{namespace}` is already deactivated.", + ) + + # If there are no active nodes in the namespace, we can safely deactivate this namespace + node_names = get_nodes_in_namespace(session, namespace) + if len(node_names) == 0: + message = f"Namespace `{namespace}` has been deactivated." + mark_namespace_deactivated(session, node_namespace, message) + return JSONResponse( + status_code=HTTPStatus.OK, + content={"message": message}, + ) + + # If cascade=true is set, we'll deactivate all nodes in this namespace and then + # subsequently deactivate this namespace + if cascade: + for node_name in node_names: + deactivate_node( + session, + node_name, + f"Cascaded from deactivating namespace `{namespace}`", + ) + message = ( + f"Namespace `{namespace}` has been deactivated. The following nodes" + f" have also been deactivated: {','.join(node_names)}" + ) + mark_namespace_deactivated(session, node_namespace, message) + + return JSONResponse( + status_code=HTTPStatus.OK, + content={ + "message": message, + }, + ) + + return JSONResponse( + status_code=405, + content={ + "message": f"Cannot deactivate node namespace `{namespace}` as there are " + "still active nodes under that namespace.", + }, + ) + + +@router.post("/namespaces/{namespace}/restore/", status_code=HTTPStatus.CREATED) +def restore_a_namespace( + namespace: str, + cascade: bool = Query( + default=False, + description="Cascade the restore down to the nodes in the namespace", + ), + session: Session = Depends(get_session), +) -> JSONResponse: + """ + Restores a node namespace + """ + node_namespace = get_node_namespace( + session=session, + namespace=namespace, + raise_if_not_exists=True, + ) + if not node_namespace.deactivated_at: + raise DJAlreadyExistsException( + message=f"Node namespace `{namespace}` already exists and is active.", + ) + + node_names = get_nodes_in_namespace(session, namespace, include_deactivated=True) + + # If cascade=true is set, we'll restore all nodes in this namespace and then + # subsequently restore this namespace + if cascade: + for node_name in node_names: + activate_node( + name=node_name, + session=session, + message=f"Cascaded from restoring namespace `{namespace}`", + ) + + message = ( + f"Namespace `{namespace}` has been restored. The following nodes" + f" have also been restored: {','.join(node_names)}" + ) + mark_namespace_restored(session, node_namespace, message) + + return JSONResponse( + status_code=HTTPStatus.CREATED, + content={ + "message": message, + }, + ) + + # Otherwise just restore this namespace + message = f"Namespace `{namespace}` has been restored." + mark_namespace_restored(session, node_namespace, message) + return JSONResponse( + status_code=HTTPStatus.CREATED, + content={"message": message}, + ) diff --git a/datajunction-server/datajunction_server/api/nodes.py b/datajunction-server/datajunction_server/api/nodes.py new file mode 100644 index 000000000..e2af38b90 --- /dev/null +++ b/datajunction-server/datajunction_server/api/nodes.py @@ -0,0 +1,855 @@ +# pylint: disable=too-many-lines +""" +Node related APIs. +""" +import logging +import os +from http import HTTPStatus +from typing import List, Optional, Union, cast + +from fastapi import APIRouter, Depends, Response +from fastapi.responses import JSONResponse +from sqlalchemy.sql.operators import is_ +from sqlmodel import Session, select +from starlette.requests import Request + +from datajunction_server.api.helpers import ( + activate_node, + deactivate_node, + get_catalog_by_name, + get_column, + get_downstream_nodes, + get_node_by_name, + get_node_namespace, + get_upstream_nodes, + hard_delete_node, + raise_if_node_exists, + revalidate_node, + validate_node_data, +) +from datajunction_server.api.namespaces import create_node_namespace +from datajunction_server.api.tags import get_tag_by_name +from datajunction_server.config import Settings +from datajunction_server.errors import DJException, DJInvalidInputException +from datajunction_server.internal.materializations import schedule_materialization_jobs +from datajunction_server.internal.nodes import ( + _create_node_from_inactive, + _update_node, + column_level_lineage, + create_cube_node_revision, + create_node_revision, + save_node, + set_column_attributes_on_node, +) +from datajunction_server.models.base import generate_display_name +from datajunction_server.models.column import Column, ColumnAttributeInput +from datajunction_server.models.history import ( + ActivityType, + EntityType, + History, + status_change_history, +) +from datajunction_server.models.node import ( + ColumnOutput, + CreateCubeNode, + CreateNode, + CreateSourceNode, + LineageColumn, + Node, + NodeMode, + NodeOutput, + NodeRevision, + NodeRevisionBase, + NodeRevisionOutput, + NodeStatus, + NodeType, + NodeValidation, + UpdateNode, +) +from datajunction_server.service_clients import QueryServiceClient +from datajunction_server.sql.dag import get_dimensions, get_nodes_with_dimension +from datajunction_server.sql.parsing.backends.antlr4 import parse +from datajunction_server.utils import ( + Version, + get_namespace_from_name, + get_query_service_client, + get_session, + get_settings, +) + +_logger = logging.getLogger(__name__) +router = APIRouter(tags=["nodes"]) + + +@router.post("/nodes/validate/", response_model=NodeValidation) +def validate_node( + data: Union[NodeRevisionBase, NodeRevision], + response: Response, + session: Session = Depends(get_session), +) -> NodeValidation: + """ + Validate a node. + """ + + if data.type == NodeType.SOURCE: + raise DJException(message="Source nodes cannot be validated") + + (validated_node, dependencies_map, _, _, errors) = validate_node_data(data, session) + if errors: + response.status_code = HTTPStatus.UNPROCESSABLE_ENTITY + status = NodeStatus.INVALID + else: + response.status_code = HTTPStatus.OK + status = NodeStatus.VALID + + return NodeValidation( + message=f"Node `{validated_node.name}` is {status}.", + status=status, + node_revision=validated_node, + dependencies=set(dependencies_map.keys()), + columns=validated_node.columns, + errors=errors, + ) + + +@router.post("/nodes/{name}/validate/", response_model=NodeValidation) +def revalidate( + name: str, + session: Session = Depends(get_session), +) -> NodeValidation: + """ + Revalidate a single existing node and update its status appropriately + """ + status = revalidate_node(name=name, session=session) + return JSONResponse( + status_code=HTTPStatus.OK, + content={ + "message": f"Node `{name}` has been set to {status}", + "status": status, + }, + ) + + +@router.post( + "/nodes/{node_name}/attributes/", + response_model=List[ColumnOutput], + status_code=201, +) +def set_column_attributes( + node_name: str, + attributes: List[ColumnAttributeInput], + *, + session: Session = Depends(get_session), +) -> List[ColumnOutput]: + """ + Set column attributes for the node. + """ + node = get_node_by_name(session, node_name) + modified_columns = set_column_attributes_on_node(session, attributes, node) + return list(modified_columns) # type: ignore + + +@router.get("/nodes/", response_model=List[str]) +def list_nodes( + node_type: Optional[NodeType] = None, + *, + session: Session = Depends(get_session), +) -> List[str]: + """ + List the available nodes. + """ + statement = select(Node.name).where(is_(Node.deactivated_at, None)) + if node_type: + statement = statement.where(Node.type == node_type) + nodes = session.exec(statement).unique().all() + return nodes + + +@router.get("/nodes/{name}/", response_model=NodeOutput) +def get_node(name: str, *, session: Session = Depends(get_session)) -> NodeOutput: + """ + Show the active version of the specified node. + """ + node = get_node_by_name(session, name, with_current=True) + return node # type: ignore + + +@router.delete("/nodes/{name}/") +def delete_node(name: str, *, session: Session = Depends(get_session)): + """ + Delete (aka deactivate) the specified node. + """ + deactivate_node(session, name) + return JSONResponse( + status_code=HTTPStatus.OK, + content={"message": f"Node `{name}` has been successfully deleted."}, + ) + + +@router.delete("/nodes/{name}/hard/", name="Hard Delete a DJ Node") +def hard_delete( + name: str, + session: Session = Depends(get_session), +) -> JSONResponse: + """ + Hard delete a node, destroying all links and invalidating all downstream nodes. + This should be used with caution, deactivating a node is preferred. + """ + impact = hard_delete_node(name=name, session=session) + return JSONResponse( + status_code=HTTPStatus.OK, + content={ + "message": f"The node `{name}` has been completely removed.", + "impact": impact, + }, + ) + + +@router.post("/nodes/{name}/restore/") +def restore_node(name: str, *, session: Session = Depends(get_session)): + """ + Restore (aka re-activate) the specified node. + """ + activate_node(session, name) + return JSONResponse( + status_code=HTTPStatus.OK, + content={"message": f"Node `{name}` has been successfully restored."}, + ) + + +@router.get("/nodes/{name}/revisions/", response_model=List[NodeRevisionOutput]) +def list_node_revisions( + name: str, *, session: Session = Depends(get_session) +) -> List[NodeRevisionOutput]: + """ + List all revisions for the node. + """ + node = get_node_by_name(session, name, with_current=False) + return node.revisions # type: ignore + + +@router.post("/nodes/source/", response_model=NodeOutput, name="Create A Source Node") +def create_source( + data: CreateSourceNode, + session: Session = Depends(get_session), +) -> NodeOutput: + """ + Create a source node. If columns are not provided, the source node's schema + will be inferred using the configured query service. + """ + raise_if_node_exists(session, data.name) + + # if the node previously existed and now is inactive + if recreated_node := _create_node_from_inactive( + new_node_type=NodeType.SOURCE, + data=data, + session=session, + ): + return recreated_node + + namespace = get_namespace_from_name(data.name) + get_node_namespace( + session=session, + namespace=namespace, + ) # Will return 404 if namespace doesn't exist + data.namespace = namespace + + node = Node( + name=data.name, + namespace=data.namespace, + type=NodeType.SOURCE, + current_version=0, + ) + catalog = get_catalog_by_name(session=session, name=data.catalog) + + columns = [ + Column( + name=column_data.name, + type=column_data.type, + dimension=( + get_node_by_name( + session, + name=column_data.dimension, + node_type=NodeType.DIMENSION, + raise_if_not_exists=False, + ) + ), + ) + for column_data in data.columns + ] + + node_revision = NodeRevision( + name=data.name, + namespace=data.namespace, + display_name=data.display_name + if data.display_name + else generate_display_name(data.name), + description=data.description, + type=NodeType.SOURCE, + status=NodeStatus.VALID, + catalog_id=catalog.id, + schema_=data.schema_, + table=data.table, + columns=columns, + parents=[], + ) + + # Point the node to the new node revision. + save_node(session, node_revision, node, data.mode) + + return node # type: ignore + + +@router.post( + "/nodes/transform/", + response_model=NodeOutput, + status_code=201, + name="Create A Transform Node", +) +@router.post( + "/nodes/dimension/", + response_model=NodeOutput, + status_code=201, + name="Create A Dimension Node", +) +@router.post( + "/nodes/metric/", + response_model=NodeOutput, + status_code=201, + name="Create A Metric Node", +) +def create_node( + data: CreateNode, + request: Request, + *, + session: Session = Depends(get_session), +) -> NodeOutput: + """ + Create a node. + """ + node_type = NodeType(os.path.basename(os.path.normpath(request.url.path))) + + if node_type == NodeType.DIMENSION and not data.primary_key: + raise DJInvalidInputException("Dimension nodes must define a primary key!") + + if node_type == NodeType.METRIC: + data.query = NodeRevision.format_metric_alias(data.query, data.name) + + raise_if_node_exists(session, data.name) + + # if the node previously existed and now is inactive + if recreated_node := _create_node_from_inactive( + new_node_type=node_type, + data=data, + session=session, + ): + return recreated_node # pragma: no cover + + namespace = get_namespace_from_name(data.name) + get_node_namespace( + session=session, + namespace=namespace, + ) # Will return 404 if namespace doesn't exist + data.namespace = namespace + + node = Node( + name=data.name, + namespace=data.namespace, + type=NodeType(node_type), + current_version=0, + ) + node_revision = create_node_revision(data, node_type, session) + save_node(session, node_revision, node, data.mode) + session.refresh(node) + + column_names = {col.name for col in node_revision.columns} + if data.primary_key and any( + key_column not in column_names for key_column in data.primary_key + ): + raise DJInvalidInputException( + f"Some columns in the primary key [{','.join(data.primary_key)}] " + f"were not found in the list of available columns for the node {node.name}.", + ) + if data.primary_key: + attributes = [ + ColumnAttributeInput( + attribute_type_namespace="system", + attribute_type_name="primary_key", + column_name=key_column, + ) + for key_column in data.primary_key + if key_column in column_names + ] + set_column_attributes_on_node(session, attributes, node) + session.refresh(node) + session.refresh(node.current) + return node # type: ignore + + +@router.post( + "/nodes/cube/", + response_model=NodeOutput, + status_code=201, + name="Create A Cube", +) +def create_cube( + data: CreateCubeNode, + *, + session: Session = Depends(get_session), + query_service_client: QueryServiceClient = Depends(get_query_service_client), +) -> NodeOutput: + """ + Create a cube node. + """ + raise_if_node_exists(session, data.name) + + # if the node previously existed and now is inactive + if recreated_node := _create_node_from_inactive( + new_node_type=NodeType.CUBE, + data=data, + session=session, + ): + return recreated_node # pragma: no cover + + namespace = get_namespace_from_name(data.name) + get_node_namespace( + session=session, + namespace=namespace, + ) + data.namespace = namespace + + node = Node( + name=data.name, + namespace=data.namespace, + type=NodeType.CUBE, + current_version=0, + ) + node_revision = create_cube_node_revision(session=session, data=data) + save_node(session, node_revision, node, data.mode) + + # Schedule materialization jobs, if any + schedule_materialization_jobs( + node_revision.materializations, + query_service_client, + ) + return node # type: ignore + + +@router.post( + "/register/table/{catalog}/{schema_}/{table}/", + response_model=NodeOutput, + status_code=201, +) +def register_table( # pylint: disable=too-many-arguments + catalog: str, + schema_: str, + table: str, + session: Session = Depends(get_session), + settings: Settings = Depends(get_settings), + query_service_client: QueryServiceClient = Depends(get_query_service_client), +) -> NodeOutput: + """ + Register a table. This creates a source node in the SOURCE_NODE_NAMESPACE and + the source node's schema will be inferred using the configured query service. + """ + if not query_service_client: + raise DJException( + message="Registering tables requires that a query " + "service is configured for table columns inference", + ) + namespace = f"{settings.source_node_namespace}.{catalog}.{schema_}" + name = f"{namespace}.{table}" + raise_if_node_exists(session, name) + + # Create the namespace if required (idempotent) + create_node_namespace(namespace=namespace, session=session) + + # Use reflection to get column names and types + _catalog = get_catalog_by_name(session=session, name=catalog) + columns = query_service_client.get_columns_for_table( + _catalog, + schema_, + table, + _catalog.engines[0] if len(_catalog.engines) >= 1 else None, + ) + + return create_source( + data=CreateSourceNode( + catalog=catalog, + schema_=schema_, + table=table, + name=name, + display_name=name, + columns=columns, + description="This source node was automatically created as a registered table.", + mode=NodeMode.PUBLISHED, + ), + session=session, + ) + + +@router.post("/nodes/{name}/columns/{column}/", status_code=201) +def link_dimension( + name: str, + column: str, + dimension: str, + dimension_column: Optional[str] = None, + session: Session = Depends(get_session), +) -> JSONResponse: + """ + Add information to a node column + """ + node = get_node_by_name(session=session, name=name) + dimension_node = get_node_by_name( + session=session, + name=dimension, + node_type=NodeType.DIMENSION, + ) + if ( + dimension_node.current.catalog is not None + and node.current.catalog.name != dimension_node.current.catalog.name + ): + raise DJException( + message=( + "Cannot add dimension to column, because catalogs do not match: " + f"{node.current.catalog.name}, {dimension_node.current.catalog.name}" + ), + ) + + target_column = get_column(node.current, column) + if dimension_column: + # Check that the dimension column exists + column_from_dimension = get_column(dimension_node.current, dimension_column) + + # Check the dimension column's type is compatible with the target column's type + if not column_from_dimension.type.is_compatible(target_column.type): + raise DJInvalidInputException( + f"The column {target_column.name} has type {target_column.type} " + f"and is being linked to the dimension {dimension} via the dimension" + f" column {dimension_column}, which has type {column_from_dimension.type}." + " These column types are incompatible and the dimension cannot be linked", + ) + + target_column.dimension = dimension_node + target_column.dimension_id = dimension_node.id + target_column.dimension_column = dimension_column + + session.add(node) + session.add( + History( + entity_type=EntityType.LINK, + entity_name=node.name, + node=node.name, + activity_type=ActivityType.CREATE, + details={ + "column": target_column.name, + "dimension": dimension_node.name, + "dimension_column": dimension_column or "", + }, + ), + ) + session.commit() + session.refresh(node) + return JSONResponse( + status_code=201, + content={ + "message": ( + f"Dimension node {dimension} has been successfully " + f"linked to column {column} on node {name}" + ), + }, + ) + + +@router.delete("/nodes/{name}/columns/{column}/", status_code=201) +def delete_dimension_link( + name: str, + column: str, + dimension: str, + dimension_column: Optional[str] = None, + session: Session = Depends(get_session), +) -> JSONResponse: + """ + Remove the link between a node column and a dimension node + """ + node = get_node_by_name(session=session, name=name) + target_column = get_column(node.current, column) + if (not target_column.dimension or target_column.dimension.name != dimension) and ( + not target_column.dimension_column + or target_column.dimension_column != dimension_column + ): + return JSONResponse( + status_code=304, + content={ + "message": ( + f"No change was made to {column} on node {name} as the " + f"specified dimension link to {dimension} on " + f"{dimension_column} was not found." + ), + }, + ) + + # Find cubes that are affected by this dimension link removal and update their statuses + affected_cubes = get_nodes_with_dimension( + session, + target_column.dimension, + [NodeType.CUBE], + ) + if affected_cubes: + for cube in affected_cubes: + if cube.status != NodeStatus.INVALID: # pragma: no cover + cube.status = NodeStatus.INVALID + session.add(cube) + session.add( + status_change_history( + node, + NodeStatus.VALID, + NodeStatus.INVALID, + ), + ) + + target_column.dimension = None # type: ignore + target_column.dimension_id = None + target_column.dimension_column = None + session.add(node) + session.add( + History( + entity_type=EntityType.LINK, + entity_name=node.name, + node=node.name, + activity_type=ActivityType.DELETE, + details={ + "column": column, + "dimension": dimension, + "dimension_column": dimension_column or "", + }, + ), + ) + session.commit() + session.refresh(node) + + return JSONResponse( + status_code=201, + content={ + "message": ( + f"The dimension link on the node {name}'s {column} to " + f"{dimension} has been successfully removed." + ), + }, + ) + + +@router.post("/nodes/{name}/tag/", status_code=201, tags=["tags"], name="Tag A Node") +def tag_node( + name: str, tag_name: str, *, session: Session = Depends(get_session) +) -> JSONResponse: + """ + Add a tag to a node + """ + node = get_node_by_name(session=session, name=name) + tag = get_tag_by_name(session, name=tag_name, raise_if_not_exists=True) + node.tags.append(tag) + + session.add(node) + session.add( + History( + entity_type=EntityType.NODE, + entity_name=node.name, + node=node.name, + activity_type=ActivityType.TAG, + details={ + "tag": tag_name, + }, + ), + ) + session.commit() + session.refresh(node) + session.refresh(tag) + + return JSONResponse( + status_code=201, + content={ + "message": ( + f"Node `{name}` has been successfully tagged with tag `{tag_name}`" + ), + }, + ) + + +@router.post( + "/nodes/{name}/refresh/", + response_model=NodeOutput, + status_code=201, +) +def refresh_source_node( + name: str, + *, + session: Session = Depends(get_session), + query_service_client: QueryServiceClient = Depends(get_query_service_client), +): + """ + Refresh a source node with the latest columns from the query service. + """ + source_node = get_node_by_name(session, name, node_type=NodeType.SOURCE) + current_revision = source_node.current + + # Get the latest columns for the source node's table from the query service + columns = query_service_client.get_columns_for_table( + current_revision.catalog.name, + current_revision.schema_, # type: ignore + current_revision.table, # type: ignore + current_revision.catalog.engines[0] + if len(current_revision.catalog.engines) >= 1 + else None, + ) + + # Check if any of the columns have changed (only continue with update if they have) + column_changes = {col.identifier() for col in current_revision.columns} != { + col.identifier() for col in columns + } + if not column_changes: + return source_node + + # Create a new node revision with the updated columns and bump the version + old_version = Version.parse(source_node.current_version) + new_revision = NodeRevision.parse_obj(current_revision.dict(exclude={"id"})) + new_revision.version = str(old_version.next_major_version()) + new_revision.columns = [ + Column(name=column.name, type=column.type, node_revisions=[new_revision]) + for column in columns + ] + + # Keep the dimension links and attributes on the columns from the node's + # last revision if any existed + new_revision.copy_dimension_links_from_revision(current_revision) + + # Point the source node to the new revision + source_node.current_version = new_revision.version + new_revision.extra_validation() + + session.add(new_revision) + session.add(source_node) + + session.add( + History( + entity_type=EntityType.NODE, + entity_name=source_node.name, + node=source_node.name, + activity_type=ActivityType.REFRESH, + details={ + "version": new_revision.version, + }, + ), + ) + session.commit() + session.refresh(source_node.current) + return source_node # type: ignore + + +@router.patch("/nodes/{name}/", response_model=NodeOutput) +def update_node( + name: str, + data: UpdateNode, + *, + session: Session = Depends(get_session), + query_service_client: QueryServiceClient = Depends(get_query_service_client), +) -> NodeOutput: + """ + Update a node. + """ + node = _update_node( + name, + data, + session=session, + query_service_client=query_service_client, + ) + return node # type: ignore + + +@router.get("/nodes/similarity/{node1_name}/{node2_name}") +def calculate_node_similarity( + node1_name: str, node2_name: str, *, session: Session = Depends(get_session) +) -> JSONResponse: + """ + Compare two nodes by how similar their queries are + """ + node1 = get_node_by_name(session=session, name=node1_name) + node2 = get_node_by_name(session=session, name=node2_name) + if NodeType.SOURCE in (node1.type, node2.type): + raise DJException( + message="Cannot determine similarity of source nodes", + http_status_code=HTTPStatus.CONFLICT, + ) + node1_ast = parse(node1.current.query) # type: ignore + node2_ast = parse(node2.current.query) # type: ignore + similarity = node1_ast.similarity_score(node2_ast) + return JSONResponse(status_code=200, content={"similarity": similarity}) + + +@router.get( + "/nodes/{name}/downstream/", + response_model=List[NodeOutput], + name="List Downstream Nodes For A Node", +) +def list_downstream_nodes( + name: str, *, node_type: NodeType = None, session: Session = Depends(get_session) +) -> List[NodeOutput]: + """ + List all nodes that are downstream from the given node, filterable by type. + """ + return get_downstream_nodes(session, name, node_type) # type: ignore + + +@router.get( + "/nodes/{name}/upstream/", + response_model=List[NodeOutput], + name="List Upstream Nodes For A Node", +) +def list_upstream_nodes( + name: str, *, node_type: NodeType = None, session: Session = Depends(get_session) +) -> List[NodeOutput]: + """ + List all nodes that are upstream from the given node, filterable by type. + """ + return get_upstream_nodes(session, name, node_type) # type: ignore + + +@router.get( + "/nodes/{name}/dag/", + response_model=List[NodeOutput], + name="List All Connected Nodes (Upstreams + Downstreams)", +) +def list_node_dag( + name: str, *, session: Session = Depends(get_session) +) -> List[NodeOutput]: + """ + List all nodes that are part of the DAG of the given node. This means getting all upstreams, + downstreams, and linked dimension nodes. + """ + node = get_node_by_name(session, name) + dimension_nodes = get_dimensions(node, attributes=False) + downstreams = get_downstream_nodes(session, name) + upstreams = get_upstream_nodes(session, name) + return list(set(cast(List[Node], dimension_nodes) + downstreams + upstreams)) # type: ignore + + +@router.get( + "/nodes/{name}/lineage/", + response_model=List[LineageColumn], + name="List column level lineage of node", +) +def column_lineage( + name: str, *, session: Session = Depends(get_session) +) -> List[LineageColumn]: + """ + List column-level lineage of a node in a graph + """ + node = get_node_by_name(session, name) + return [ + column_level_lineage( + session, + node.current, + col.name, + ) + for col in node.current.columns + ] diff --git a/datajunction-server/datajunction_server/api/sql.py b/datajunction-server/datajunction_server/api/sql.py new file mode 100644 index 000000000..7273dbdee --- /dev/null +++ b/datajunction-server/datajunction_server/api/sql.py @@ -0,0 +1,94 @@ +""" +SQL related APIs. +""" +import logging +from typing import List, Optional + +from fastapi import APIRouter, Depends, Query +from sqlmodel import Session + +from datajunction_server.api.helpers import ( + build_sql_for_multiple_metrics, + get_engine, + get_query, + validate_orderby, +) +from datajunction_server.models.metric import TranslatedSQL +from datajunction_server.models.query import ColumnMetadata +from datajunction_server.utils import get_session + +_logger = logging.getLogger(__name__) +router = APIRouter(tags=["sql"]) + + +@router.get( + "/sql/{node_name}/", + response_model=TranslatedSQL, + name="Get SQL For A Node", +) +def get_sql( + node_name: str, + dimensions: List[str] = Query([]), + filters: List[str] = Query([]), + orderby: List[str] = Query([]), + limit: Optional[int] = None, + *, + session: Session = Depends(get_session), + engine_name: Optional[str] = None, + engine_version: Optional[str] = None, +) -> TranslatedSQL: + """ + Return SQL for a node. + """ + engine = ( + get_engine(session, engine_name, engine_version) # type: ignore + if engine_name + else None + ) + validate_orderby(orderby, [node_name], dimensions) + query_ast = get_query( + session=session, + node_name=node_name, + dimensions=dimensions, + filters=filters, + orderby=orderby, + limit=limit, + engine=engine, + ) + columns = [ + ColumnMetadata(name=col.alias_or_name.name, type=str(col.type)) # type: ignore + for col in query_ast.select.projection + ] + return TranslatedSQL( + sql=str(query_ast), + columns=columns, + dialect=engine.dialect if engine else None, + ) + + +@router.get("/sql/", response_model=TranslatedSQL, name="Get SQL For Metrics") +def get_sql_for_metrics( + metrics: List[str] = Query([]), + dimensions: List[str] = Query([]), + filters: List[str] = Query([]), + orderby: List[str] = Query([]), + limit: Optional[int] = None, + *, + session: Session = Depends(get_session), + engine_name: Optional[str] = None, + engine_version: Optional[str] = None, +) -> TranslatedSQL: + """ + Return SQL for a set of metrics with dimensions and filters + """ + translated_sql, _, _ = build_sql_for_multiple_metrics( + session, + metrics, + dimensions, + filters, + orderby, + limit, + engine_name, + engine_version, + ) + return translated_sql diff --git a/datajunction-server/datajunction_server/api/tags.py b/datajunction-server/datajunction_server/api/tags.py new file mode 100644 index 000000000..d696e6006 --- /dev/null +++ b/datajunction-server/datajunction_server/api/tags.py @@ -0,0 +1,142 @@ +""" +Tag related APIs. +""" + +from typing import List, Optional + +from fastapi import APIRouter, Depends +from sqlalchemy.orm import joinedload +from sqlmodel import Session, select + +from datajunction_server.errors import DJException +from datajunction_server.models import History +from datajunction_server.models.history import ActivityType, EntityType +from datajunction_server.models.node import NodeType +from datajunction_server.models.tag import CreateTag, Tag, TagOutput, UpdateTag +from datajunction_server.utils import get_session + +router = APIRouter(tags=["tags"]) + + +def get_tag_by_name( + session: Session, + name: str, + raise_if_not_exists: bool = False, + for_update: bool = False, +): + """ + Retrieves a tag by its name. + """ + statement = select(Tag).where(Tag.name == name) + if for_update: + statement = statement.with_for_update().execution_options( + populate_existing=True, + ) + tag = session.exec(statement).one_or_none() + if not tag and raise_if_not_exists: + raise DJException( + message=(f"A tag with name `{name}` does not exist."), + http_status_code=404, + ) + return tag + + +@router.get("/tags/", response_model=List[TagOutput]) +def list_tags( + tag_type: Optional[str] = None, *, session: Session = Depends(get_session) +) -> List[TagOutput]: + """ + List all available tags. + """ + statement = select(Tag) + if tag_type: + statement = statement.where(Tag.tag_type == tag_type) + return session.exec(statement).all() + + +@router.get("/tags/{name}/", response_model=Tag) +def get_a_tag(name: str, *, session: Session = Depends(get_session)) -> Tag: + """ + Return a tag by name. + """ + tag = get_tag_by_name(session, name, raise_if_not_exists=True) + return tag + + +@router.post("/tags/", response_model=Tag, status_code=201) +def create_a_tag( + data: CreateTag, + session: Session = Depends(get_session), +) -> Tag: + """ + Create a tag. + """ + tag = get_tag_by_name(session, data.name, raise_if_not_exists=False) + if tag: + raise DJException( + message=f"A tag with name `{data.name}` already exists!", + http_status_code=500, + ) + tag = Tag.from_orm(data) + session.add(tag) + session.add( + History( + entity_type=EntityType.TAG, + entity_name=tag.name, + activity_type=ActivityType.CREATE, + ), + ) + session.commit() + session.refresh(tag) + return tag + + +@router.patch("/tags/{name}/", response_model=Tag) +def update_a_tag( + name: str, + data: UpdateTag, + session: Session = Depends(get_session), +) -> Tag: + """ + Update a tag. + """ + tag = get_tag_by_name(session, name, raise_if_not_exists=True, for_update=True) + + if data.description: + tag.description = data.description + if data.tag_metadata: + tag.tag_metadata = data.tag_metadata + session.add(tag) + session.add( + History( + entity_type=EntityType.TAG, + entity_name=tag.name, + activity_type=ActivityType.UPDATE, + details=data, + ), + ) + session.commit() + session.refresh(tag) + return tag + + +@router.get("/tags/{name}/nodes/", response_model=List[str]) +def list_nodes_for_a_tag( + name: str, + node_type: Optional[NodeType] = None, + *, + session: Session = Depends(get_session), +) -> List[str]: + """ + Find nodes tagged with the tag, filterable by node type. + """ + statement = select(Tag).where(Tag.name == name).options(joinedload(Tag.nodes)) + tag = session.exec(statement).unique().one_or_none() + if not tag: + raise DJException( + message=f"A tag with name `{name}` does not exist.", + http_status_code=404, + ) + if not node_type: + return sorted([node.name for node in tag.nodes]) + return sorted([node.name for node in tag.nodes if node.type == node_type]) diff --git a/datajunction-server/datajunction_server/config.py b/datajunction-server/datajunction_server/config.py new file mode 100644 index 000000000..dfbebcb62 --- /dev/null +++ b/datajunction-server/datajunction_server/config.py @@ -0,0 +1,94 @@ +""" +Configuration for the datajunction server. +""" +import urllib.parse +from datetime import timedelta +from pathlib import Path +from typing import List, Optional + +from cachelib.base import BaseCache +from cachelib.file import FileSystemCache +from cachelib.redis import RedisCache +from celery import Celery +from pydantic import BaseSettings + + +class Settings( + BaseSettings, +): # pylint: disable=too-few-public-methods #pragma: no cover + """ + DataJunction configuration. + """ + + name: str = "DJ server" + description: str = "A DataJunction metrics layer" + url: str = "http://localhost:8000/" + + # A list of hostnames that are allowed to make cross-site HTTP requests + cors_origin_whitelist: List[str] = ["http://localhost:3000"] + + # SQLAlchemy URI for the metadata database. + index: str = "sqlite:///dj.db?check_same_thread=False" + + # Directory where the repository lives. This should have 2 subdirectories, "nodes" and + # "databases". + repository: Path = Path(".") + + # Where to store the results from queries. + results_backend: BaseCache = FileSystemCache("/tmp/dj", default_timeout=0) + + # Cache for paginating results and potentially other things. + redis_cache: Optional[str] = None + paginating_timeout: timedelta = timedelta(minutes=5) + + # Configure Celery for async requests. If not configured async queries will be + # executed using FastAPI's ``BackgroundTasks``. + celery_broker: Optional[str] = None + + # How long to wait when pinging databases to find out the fastest online database. + do_ping_timeout: timedelta = timedelta(seconds=5) + + # Query service + query_service: Optional[str] = None + + # The namespace where source nodes for registered tables should exist + source_node_namespace: Optional[str] = "source" + + # This specifies what the DJ_LOGICAL_TIMESTAMP() macro should be replaced with. + # This defaults to an Airflow compatible value, but other examples include: + # ${dj_logical_timestamp} + # {{ dj_logical_timestamp }} + # $dj_logical_timestamp + dj_logical_timestamp_format: Optional[str] = "${dj_logical_timestamp}" + + # DJ secret, used to encrypt passwords and JSON web tokens + secret: Optional[str] = None + + # GitHub OAuth application client ID + github_oauth_client_id: Optional[str] = None + + # GitHub OAuth application client secret + github_oauth_client_secret: Optional[str] = None + + @property + def celery(self) -> Celery: + """ + Return Celery app. + """ + return Celery(__name__, broker=self.celery_broker) + + @property + def cache(self) -> Optional[BaseCache]: + """ + Configure the Redis cache. + """ + if self.redis_cache is None: + return None + + parsed = urllib.parse.urlparse(self.redis_cache) + return RedisCache( + host=parsed.hostname, + port=parsed.port, + password=parsed.password, + db=parsed.path.strip("/"), + ) diff --git a/datajunction-server/datajunction_server/constants.py b/datajunction-server/datajunction_server/constants.py new file mode 100644 index 000000000..f47907585 --- /dev/null +++ b/datajunction-server/datajunction_server/constants.py @@ -0,0 +1,26 @@ +""" +Useful constants. +""" + +from datetime import timedelta +from uuid import UUID + +DJ_DATABASE_ID = 0 +DJ_DATABASE_UUID = UUID("594804bf-47cb-426c-83c4-94a348e95972") +SQLITE_DATABASE_ID = -1 +SQLITE_DATABASE_UUID = UUID("3619eeba-d628-4ab1-9dd5-65738ab3c02f") + +DEFAULT_DIMENSION_COLUMN = "id" + +# used by the SQLAlchemy client +QUERY_EXECUTE_TIMEOUT = timedelta(seconds=60) +GET_COLUMNS_TIMEOUT = timedelta(seconds=60) + +UNAUTHENTICATED_ENDPOINTS = [ + "/docs", + "/openapi.json", + "/basic/user/", + "/basic/login/", + "/github/login/", + "/github/token/", +] diff --git a/datajunction-server/datajunction_server/construction/__init__.py b/datajunction-server/datajunction_server/construction/__init__.py new file mode 100755 index 000000000..e69de29bb diff --git a/datajunction-server/datajunction_server/construction/build.py b/datajunction-server/datajunction_server/construction/build.py new file mode 100755 index 000000000..7a8cfe015 --- /dev/null +++ b/datajunction-server/datajunction_server/construction/build.py @@ -0,0 +1,837 @@ +"""Functions to add to an ast DJ node queries""" +import collections +import logging +import time + +# pylint: disable=too-many-arguments,too-many-locals,too-many-nested-blocks,too-many-branches,R0401 +from typing import DefaultDict, Deque, Dict, List, Optional, Set, Tuple, Union, cast + +from sqlmodel import Session + +from datajunction_server.construction.utils import to_namespaced_name +from datajunction_server.errors import DJException, DJInvalidInputException +from datajunction_server.models.column import Column +from datajunction_server.models.engine import Dialect +from datajunction_server.models.materialization import GenericCubeConfig +from datajunction_server.models.node import BuildCriteria, Node, NodeRevision, NodeType +from datajunction_server.sql.dag import get_shared_dimensions +from datajunction_server.sql.parsing.ast import CompileContext +from datajunction_server.sql.parsing.backends.antlr4 import ast, parse +from datajunction_server.sql.parsing.types import ColumnType +from datajunction_server.utils import amenable_name + +_logger = logging.getLogger(__name__) + + +def _get_tables_from_select( + select: ast.SelectExpression, +) -> DefaultDict[NodeRevision, List[ast.Table]]: + """ + Extract all tables (source, transform, dimensions) + directly on the select that have an attached DJ node + """ + tables: DefaultDict[NodeRevision, List[ast.Table]] = collections.defaultdict(list) + + for table in select.find_all(ast.Table): + if node := table.dj_node: # pragma: no cover + tables[node].append(table) + return tables + + +def _join_path( + dimension_node: NodeRevision, + initial_nodes: Set[NodeRevision], +) -> Tuple[NodeRevision, Dict[Tuple[NodeRevision, NodeRevision], List[Column]]]: + """ + For a dimension node, we want to find a possible join path between it + and any of the nodes that are directly referenced in the original query. If + no join path exists, returns an empty dict. + """ + processed = set() + + to_process: Deque[ + Tuple[NodeRevision, Dict[Tuple[NodeRevision], List[Column]]] + ] = collections.deque([]) + join_info: Dict[Tuple[NodeRevision], List[Column]] = {} + to_process.extend([(node, join_info.copy()) for node in initial_nodes]) + possible_join_paths = [] + + while to_process: + current_node, path = to_process.popleft() + processed.add(current_node) + dimensions_to_columns = collections.defaultdict(list) + + # From the columns on the current node, find the next layer of + # dimension nodes that can be joined in + for col in current_node.columns: + if col.dimension and col.dimension.type == NodeType.DIMENSION: + dimensions_to_columns[col.dimension.current].append(col) + + # Go through all potential dimensions and their join columns + for joinable_dim, join_cols in dimensions_to_columns.items(): + next_join_path = {**path, **{(current_node, joinable_dim): join_cols}} + full_join_path = (joinable_dim, next_join_path) + if joinable_dim == dimension_node: + for col in join_cols: + if col.dimension_column is None and not any( + dim_col.name == "id" for dim_col in dimension_node.columns + ): + raise DJException( + f"Node {current_node.name} specifying dimension " + f"{joinable_dim.name} on column {col.name} does not" + f" specify a dimension column, but {dimension_node.name} " + f"does not have the default key `id`.", + ) + possible_join_paths.append(full_join_path) # type: ignore + if joinable_dim not in processed: # pragma: no cover + to_process.append(full_join_path) + for parent in joinable_dim.parents: + to_process.append((parent.current, next_join_path)) + return min(possible_join_paths, key=len) # type: ignore + + +def _get_or_build_join_table( + session: Session, + table_node: NodeRevision, + build_criteria: Optional[BuildCriteria], +): + """ + Build the join table from a materialization if one is available, or recurse + to build it from the dimension node's query if not + """ + table_node_alias = amenable_name(table_node.name) + join_table = cast( + Optional[ast.TableExpression], + _get_node_table(table_node, build_criteria), + ) + if not join_table: # pragma: no cover + join_query = parse(cast(str, table_node.query)) + join_table = build_ast(session, join_query) # type: ignore + join_table.parenthesized = True # type: ignore + + for col in join_table.columns: + col._table = join_table # pylint: disable=protected-access + + join_table = cast(ast.TableExpression, join_table) # type: ignore + right_alias = ast.Name(table_node_alias) + join_right = ast.Alias( # type: ignore + right_alias, + child=join_table, + as_=True, + ) + join_table.set_alias(right_alias) # type: ignore + return join_right + + +def _build_joins_for_dimension( + session: Session, + dim_node: NodeRevision, + initial_nodes: Set[NodeRevision], + tables: DefaultDict[NodeRevision, List[ast.Table]], + build_criteria: Optional[BuildCriteria], + required_dimension_columns: List[ast.Column], +) -> List[ast.Join]: + """ + Returns the join ASTs needed to bring in the dimension node from + the set of initial nodes. + """ + _, paths = _join_path(dim_node, initial_nodes) + asts = [] + for connecting_nodes, join_columns in paths.items(): + start_node, table_node = connecting_nodes # type: ignore + join_on = [] + + # Assemble table on left of join + left_table = ( + tables[start_node][0].child # type: ignore + if isinstance(tables[start_node][0], ast.Alias) + else tables[start_node][0] + ) + join_left_columns = { + col.alias_or_name.name: col for col in left_table.columns # type: ignore + } + + # Assemble table on right of join + join_right = _get_or_build_join_table( + session, + table_node, + build_criteria, + ) + + # Optimize query by filtering down to only the necessary columns + selected_columns = {col.name.name for col in required_dimension_columns} + available_join_columns = { + col.dimension_column for col in join_columns if col.dimension_column + } + primary_key_columns = {col.name for col in table_node.primary_key()} + joinable_dim_columns = { + col.name for col in table_node.columns if col.dimension_id + } + required_mapping = ( + selected_columns.union(available_join_columns) + .union(primary_key_columns) + .union(joinable_dim_columns) + ) + join_right.child.select.projection = [ + col + for col in join_right.child.select.projection + if col.alias_or_name.name in required_mapping + ] + + initial_nodes.add(table_node) + tables[table_node].append(join_right) # type: ignore + join_right_columns = { + col.alias_or_name.name: col # type: ignore + for col in join_right.child.columns + } + + # Assemble join ON clause + for join_col in join_columns: + join_table_pk = table_node.primary_key() + if join_col.name in join_left_columns and ( + join_col.dimension_column in join_right_columns + or join_table_pk[0].name in join_right_columns + ): + left_table.add_ref_column( + cast(ast.Column, join_left_columns[join_col.name]), + ) + join_on.append( + ast.BinaryOp.Eq( + join_left_columns[join_col.name], + join_right_columns[ + join_col.dimension_column or join_table_pk[0].name + ], + use_alias_as_name=True, + ), + ) + else: + raise DJInvalidInputException( # pragma: no cover + f"The specified join column {join_col.dimension_column} " + f"does not exist on {table_node.name}", + ) + for dim_col in required_dimension_columns: + join_right.child.add_ref_column(dim_col) + + if join_on: # pragma: no cover + asts.append( + ast.Join( + "LEFT OUTER", + join_right, # type: ignore + ast.JoinCriteria( + on=ast.BinaryOp.And(*join_on), # pylint: disable=E1120 + ), + ), + ) + return asts + + +def join_tables_for_dimensions( + session: Session, + dimension_nodes_to_columns: Dict[NodeRevision, List[ast.Column]], + tables: DefaultDict[NodeRevision, List[ast.Table]], + build_criteria: Optional[BuildCriteria] = None, +): + """ + Joins the tables necessary for a set of filter and group by dimensions + onto the select expression. + + In some cases, the necessary tables will already be on the select and + no additional joins will be needed. However, if the tables are not in + the select, it will traverse through available linked tables (via dimension + nodes) and join them in. + """ + for dim_node, required_dimension_columns in sorted( + dimension_nodes_to_columns.items(), + key=lambda x: x[0].name, + ): + # Find all the selects that contain the different dimension columns + selects_map = { + cast(ast.Select, dim_col.get_nearest_parent_of_type(ast.Select)) + for dim_col in required_dimension_columns + } + + # Join the source tables (if necessary) for these dimension columns + # onto each select clause + for select in selects_map: + initial_nodes = set(tables) + if dim_node not in initial_nodes: # need to join dimension + join_asts = _build_joins_for_dimension( + session, + dim_node, + initial_nodes, + tables, + build_criteria, + required_dimension_columns, + ) + if join_asts and select.from_: + select.from_.relations[-1].extensions.extend( # pragma: no cover + join_asts, + ) + + +def _build_tables_on_select( + session: Session, + select: ast.SelectExpression, + tables: Dict[NodeRevision, List[ast.Table]], + memoized_queries: Dict[int, ast.Query], + build_criteria: Optional[BuildCriteria] = None, +): + """ + Add all nodes not agg or filter dimensions to the select + """ + for node, tbls in tables.items(): + node_table = cast( + Optional[ast.Table], + _get_node_table(node, build_criteria), + ) # got a materialization + if node_table is None: # no materialization - recurse to node first + node_query = parse(cast(str, node.query)) + if hash(node_query) in memoized_queries: # pragma: no cover + node_table = memoized_queries[hash(node_query)].select # type: ignore + else: + query_ast = build_ast( # type: ignore + session, + node_query, + memoized_queries, + build_criteria, + ) + node_table = query_ast.select # type: ignore + node_table.parenthesized = True # type: ignore + memoized_queries[hash(node_query)] = query_ast + + alias = amenable_name(node.node.name) + context = CompileContext(session=session, exception=DJException()) + + node_ast = ast.Alias(ast.Name(alias), child=node_table, as_=True) # type: ignore + for tbl in tbls: + if isinstance(node_ast.child, ast.Select) and isinstance(tbl, ast.Alias): + node_ast.child.projection = [ + col + for col in node_ast.child.projection + if col in set(tbl.child.select.projection) + ] + node_ast.compile(context) + + select.replace( + tbl, + node_ast, + copy=False, + ) + + +def dimension_columns_mapping( + select: ast.SelectExpression, +) -> Dict[NodeRevision, List[ast.Column]]: + """ + Extract all dimension nodes referenced by columns + """ + dimension_nodes_to_columns: Dict[NodeRevision, List[ast.Column]] = {} + + for col in select.find_all(ast.Column): + if isinstance(col.table, ast.Table): + if node := col.table.dj_node: # pragma: no cover + if node.type == NodeType.DIMENSION: + dimension_nodes_to_columns[node] = dimension_nodes_to_columns.get( + node, + [], + ) + dimension_nodes_to_columns[node].append(col) + return dimension_nodes_to_columns + + +# flake8: noqa: C901 +def _build_select_ast( + session: Session, + select: ast.SelectExpression, + memoized_queries: Dict[int, ast.Query], + build_criteria: Optional[BuildCriteria] = None, +): + """ + Transforms a select ast by replacing dj node references with their asts + Starts by extracting all dimensions-backed columns from filters + group bys. + Some of them can be sourced directly from tables on the select, others cannot + For the ones that cannot be sourced directly, attempt to join them via dimension links. + """ + tables = _get_tables_from_select(select) + dimension_columns = dimension_columns_mapping(select) + join_tables_for_dimensions(session, dimension_columns, tables, build_criteria) + _build_tables_on_select(session, select, tables, memoized_queries, build_criteria) + + +# pylint: disable=R0915 +def add_filters_dimensions_orderby_limit_to_query_ast( + query: ast.Query, + dialect: Optional[str] = None, # pylint: disable=unused-argument + filters: Optional[List[str]] = None, + dimensions: Optional[List[str]] = None, + orderby: Optional[List[str]] = None, + limit: Optional[int] = None, +): + """ + Add filters and dimensions to a query ast + """ + projection_addition = {} + + if dimensions: + for agg in dimensions: + temp_select = parse( + f"select * group by {agg}", + ).select + query.select.group_by += temp_select.group_by # type:ignore + for col in temp_select.find_all(ast.Column): + projection_addition[col.identifier(False)] = col + + if filters: + filter_asts = ( # pylint: disable=consider-using-ternary + query.select.where and [query.select.where] or [] + ) + + for filter_ in filters: + # use parse to get the asts from the strings we got + temp_select = parse(f"select * where {filter_}").select + filter_asts.append( + temp_select.where, # type:ignore + ) + for col in temp_select.find_all(ast.Column): + if not dimensions: + projection_addition[col.identifier(False)] = col + + query.select.where = ast.BinaryOp.And(*filter_asts) + + if not query.select.organization: + query.select.organization = ast.Organization([]) + + if orderby: + for order in orderby: + temp_query = parse( + f"select * order by {order}", + ) + query.select.organization.order += ( # type:ignore + temp_query.select.organization.order # type:ignore + ) + + # add all used dimension columns to the projection without duplicates + projection_update = [] + for exp in query.select.projection: + if not isinstance(exp, ast.Column): + projection_update.append(exp) + else: + ident = exp.identifier(False) + added = None + for exist_idents, exist_cols in projection_addition.items(): + if exist_idents.endswith(ident) or ident.endswith(exist_idents): + projection_update.append(exist_cols) + added = exist_idents + break + + if added is None: + projection_update.append(exp) + else: + del projection_addition[added] + + projection_update += list(projection_addition.values()) + + query.select.projection = projection_update + + if limit is not None: + query.select.limit = ast.Number(limit) + + +def _get_node_table( + node: NodeRevision, + build_criteria: Optional[BuildCriteria] = None, + as_select: bool = False, +) -> Optional[Union[ast.Select, ast.Table]]: + """ + If a node has a materialization available, return the materialized table + """ + table = None + if node.type == NodeType.SOURCE: + if node.table: + name = ast.Name( + node.table, + namespace=ast.Name(node.schema_) if node.schema_ else None, + ) + else: + name = to_namespaced_name(node.name) + table = ast.Table(name, _dj_node=node) + elif node.availability and node.availability.is_available( + criteria=build_criteria, + ): # pragma: no cover + table = ast.Table( + ast.Name( + node.availability.table, + namespace=( + ast.Name(node.availability.schema_) + if node.availability.schema_ + else None + ), + ), + _dj_node=node, + ) + if table and as_select: # pragma: no cover + return ast.Select( + projection=[ast.Wildcard()], + from_=ast.From(relations=[ast.Relation(table)]), + ) + return table + + +def build_node( # pylint: disable=too-many-arguments + session: Session, + node: NodeRevision, + filters: Optional[List[str]] = None, + dimensions: Optional[List[str]] = None, + orderby: Optional[List[str]] = None, + limit: Optional[int] = None, + build_criteria: Optional[BuildCriteria] = None, +) -> ast.Query: + """ + Determines the optimal way to build the Node and does so + """ + # Set the dialect by finding available engines for this node, or default to Spark + if not build_criteria: + build_criteria = BuildCriteria( + dialect=( + node.catalog.engines[0].dialect + if node.catalog + and node.catalog.engines + and node.catalog.engines[0].dialect + else Dialect.SPARK + ), + ) + + # get dimension columns which are required + # in the stated bound dimensions on the metric node + dimensions = dimensions or [] + dimensions = [ + col.name for col in node.required_dimensions if col.name not in dimensions + ] + dimensions + + # if no dimensions need to be added then we can see if the node is directly materialized + if not (filters or dimensions): + if select := cast( + ast.Select, + _get_node_table(node, build_criteria, as_select=True), + ): + return ast.Query(select=select) # pragma: no cover + + if node.query: + query = parse(node.query) + else: + query = build_source_node_query(node) + + add_filters_dimensions_orderby_limit_to_query_ast( + query, + build_criteria.dialect, + filters, + dimensions, + orderby, + limit, + ) + memoized_queries: Dict[int, ast.Query] = {} + _logger.info("Calling build_ast on %s", node.name) + built_ast = build_ast(session, query, memoized_queries, build_criteria) + _logger.info("Finished build_ast on %s", node.name) + return built_ast + + +def build_metric_nodes( + session: Session, + metric_nodes: List[Node], + filters: List[str], + dimensions: List[str], + orderby: List[str], + limit: Optional[int] = None, + build_criteria: Optional[BuildCriteria] = None, +): + """ + Build a single query for all metrics in the list, including the + specified group bys (dimensions) and filters. As long as all + metric nodes share the same set of dimensions, we can: + (a) build each metric node query separately + (b) wrap each built metric node query in a WITH statement + (c) join the node queries together via the dimension columns + """ + if any(metric_node.type != NodeType.METRIC for metric_node in metric_nodes): + raise DJInvalidInputException( # pragma: no cover + "Cannot build a query for multiple nodes if one or more " + "of them aren't metric nodes.", + ) + + shared_dimensions = [dim.name for dim in get_shared_dimensions(metric_nodes)] + for dimension_attribute in dimensions: + if dimension_attribute not in shared_dimensions: + raise DJInvalidInputException( + f"The dimension attribute `{dimension_attribute}` is not " + "available on every metric and thus cannot be included.", + ) + + for filter_ in filters: + temp_select = parse(f"select * where {filter_}").select + columns_in_filter = temp_select.where.find_all(ast.Column) # type: ignore + dims_without_prefix = {dim.split(".")[-1]: dim for dim in shared_dimensions} + for col in columns_in_filter: + if str(col) not in shared_dimensions: + potential_dimension_match = ( + f" Did you mean `{dims_without_prefix[str(col)]}`?" + if str(col) in dims_without_prefix + else "" + ) + raise DJInvalidInputException( + f"The filter `{filter_}` references the dimension attribute " + f"`{col}`, which is not available on every" + f" metric and thus cannot be included.{potential_dimension_match}", + ) + + combined_ast: ast.Query = ast.Query( + select=ast.Select(from_=ast.From(relations=[])), + ctes=[], + ) + initial_dimension_columns = [] + all_dimension_columns = [] + + orderby_sort_items: List[ast.SortItem] = [] + orderby = orderby or [] + orderby_mapping = {} + for order in orderby: + orderby_metric = None + for metric_node in metric_nodes: + if metric_node.name.lower() in order.lower(): + orderby_metric = metric_node.name + break + orderby_mapping[order] = orderby_metric + + for idx, metric_node in enumerate(metric_nodes): + # Build each metric node separately + curr_orderby = None + + if (not orderby_sort_items) and orderby_mapping: + curr_orderby = [ + order + for order, metric_name in orderby_mapping.items() + if metric_name is None + ] + + metric_ast = build_node( + session=session, + node=metric_node.current, + filters=filters, + dimensions=dimensions, + orderby=curr_orderby, + build_criteria=build_criteria, + ) + + # Add the WITH statements to the combined query + metric_ast_alias = ast.Name(f"m{idx}_" + metric_node.name.replace(".", "_DOT_")) + metric_ast.alias = metric_ast_alias + metric_ast.parenthesized = True + metric_ast.as_ = True + combined_ast.ctes += [metric_ast] + + # Add the metric and dimensions to the final query layer's SELECT + current_table = ast.Table(metric_ast_alias) + + organization = cast(ast.Organization, metric_ast.select.organization) + metric_ast.select.organization = None + # if an orderby referred to this metric node, parse and add it to the order items + if metric_order := ( + [None] + + [ + order_key # type: ignore + for order_key, order_metric in orderby_mapping.items() + if metric_node.name == order_metric + ] + ).pop(): + metric_sort_item = parse(f"select * order by {metric_order}").select.organization.order[0] # type: ignore #pylint: disable=C0301 + metric_col = ast.Column( + name=ast.Name( + [ + exp.alias_or_name.identifier(False) + for exp in metric_ast.select.projection + if exp.is_aggregation() + ][0], + ), + _table=current_table, + ) + for col in metric_sort_item.find_all(ast.Column): + col.swap(metric_col) + orderby_mapping[metric_order] = metric_sort_item # type: ignore + + # bind the table for this built metric to all columns in the + metric_ast.organization = None + for col in organization.find_all(ast.Column): + col.add_table(current_table) + orderby_sort_items += organization.order # type: ignore + + final_select_columns = [ + ast.Column( + name=col.alias_or_name, # type: ignore + _table=current_table, + _type=col.type, # type: ignore + ) + for col in metric_ast.select.projection + ] + metric_column_idents = { + col.alias_or_name.name # type: ignore + for col in parse(metric_node.current.query).select.projection + } + + metric_columns = [] + + dimension_columns = [] + + for col in final_select_columns: + if col.name.name in metric_column_idents: + metric_columns.append(col) + else: + dimension_columns.append(col) + + all_dimension_columns += dimension_columns + combined_ast.select.projection.extend(metric_columns) + + if not combined_ast.select.from_.relations: # type: ignore + initial_dimension_columns = dimension_columns + combined_ast.select.from_.relations.append( # type: ignore + ast.Relation(current_table), + ) + else: + comparisons = [ + ast.BinaryOp.Eq(initial_dim_col, current_dim_col) + for initial_dim_col, current_dim_col in zip( + initial_dimension_columns, + dimension_columns, + ) + ] + combined_ast.select.from_.relations[0].extensions.append( # type: ignore + ast.Join( + "FULL OUTER", + ast.Table(metric_ast_alias), + ast.JoinCriteria( + on=ast.BinaryOp.And(*comparisons), + ), + ), + ) + + dimension_grouping: Dict[str, List] = {} + for col in all_dimension_columns: + dimension_grouping.setdefault(str(col.alias_or_name.name), []).append(col) + dimension_columns = [ + ast.Function(name=ast.Name("COALESCE"), args=list(columns)).set_alias( + ast.Name(col_name), + ) + if len(columns) > 1 + else columns[0] + for col_name, columns in dimension_grouping.items() + ] + + combined_ast.select.projection.extend(dimension_columns) + + # go through the orderby items and make sure we put them in the order the user requested them in + + for idx, sort_item in enumerate(orderby_mapping.values()): + if isinstance(sort_item, ast.SortItem): + orderby_sort_items.insert(idx, sort_item) + + combined_ast.select.organization = ast.Organization(orderby_sort_items) + + if limit is not None: + combined_ast.select.limit = ast.Number(limit) + + return combined_ast + + +def build_materialized_cube_node( + selected_metrics: List[Column], + selected_dimensions: List[Column], + cube: NodeRevision, +) -> ast.Query: + """ + Build query for a materialized cube node + """ + combined_ast: ast.Query = ast.Query( + select=ast.Select(from_=ast.From(relations=[])), + ctes=[], + ) + default_materialization_config = [ + materialization + for materialization in cube.materializations + if materialization.name == "default" + ][0].config + cube_config = GenericCubeConfig.parse_obj(default_materialization_config) + + selected_metric_keys = [col.name for col in selected_metrics] + + # Assemble query for materialized cube based on the previously saved measures + # combiner expression for each metric + for metric_key, metric_measures in cube_config.measures.items(): + if metric_key in selected_metric_keys: + measures_combiner_ast = parse(f"SELECT {metric_measures.combiner}") + measures_type_lookup = { + measure.name: measure.type for measure in metric_measures.measures + } + for col in measures_combiner_ast.find_all(ast.Column): + col.add_type( + ColumnType( + measures_type_lookup[col.alias_or_name.name], # type: ignore + ), + ) + combined_ast.select.projection.extend( + [ + proj.set_alias(ast.Name(metric_key)) + for proj in measures_combiner_ast.select.projection + ], + ) + + # Add in selected dimension attributes to the query + for selected_dim in selected_dimensions: + dimension_column = ast.Column(name=ast.Name(selected_dim.name)) + combined_ast.select.projection.append(dimension_column) + combined_ast.select.group_by.append(dimension_column) + + combined_ast.select.from_.relations.append( # type: ignore + ast.Relation(primary=ast.Table(ast.Name(cube.availability.table))), # type: ignore + ) + return combined_ast + + +def build_source_node_query(node: NodeRevision): + """ + Returns a query that selects each column explicitly in the source node. + """ + table = ast.Table(to_namespaced_name(node.name), None, _dj_node=node) + select = ast.Select( + projection=[ + ast.Column(ast.Name(tbl_col.name), _table=table) for tbl_col in node.columns + ], + from_=ast.From(relations=[ast.Relation(table)]), + ) + return ast.Query(select=select) + + +def build_ast( # pylint: disable=too-many-arguments + session: Session, + query: ast.Query, + memoized_queries: Dict[int, ast.Query] = None, + build_criteria: Optional[BuildCriteria] = None, +) -> ast.Query: + """ + Determines the optimal way to build the query AST and does so + """ + memoized_queries = memoized_queries or {} + + start = time.time() + context = CompileContext(session=session, exception=DJException()) + if hash(query) in memoized_queries: + query = memoized_queries[hash(query)] # pragma: no cover + else: + query.compile(context) + memoized_queries[hash(query)] = query + end = time.time() + _logger.info("Finished compiling query %s in %s", str(query)[-100:], end - start) + + start = time.time() + query.build(session, memoized_queries, build_criteria) + end = time.time() + _logger.info("Finished building query in %s", end - start) + return query diff --git a/datajunction-server/datajunction_server/construction/dj_query.py b/datajunction-server/datajunction_server/construction/dj_query.py new file mode 100644 index 000000000..dae419d07 --- /dev/null +++ b/datajunction-server/datajunction_server/construction/dj_query.py @@ -0,0 +1,268 @@ +""" +Functions for making queries directly against DJ +""" +from typing import Dict, List, Optional, Set, Tuple + +from sqlmodel import Session + +from datajunction_server.construction.build import build_metric_nodes, build_node +from datajunction_server.construction.utils import DJErrorException, get_dj_node +from datajunction_server.models.node import Node, NodeType +from datajunction_server.sql.parsing import ast +from datajunction_server.sql.parsing.backends.antlr4 import parse +from datajunction_server.utils import amenable_name + + +def try_get_dj_node( + session: Session, + name: str, + kinds: Set[NodeType], +) -> Optional[Node]: + "wraps get dj node to return None if no node is found" + try: + return get_dj_node(session, name, kinds, current=False) + except DJErrorException: + return None + + +def selects_only_metrics(select: ast.Select) -> bool: + """ + Checks that a Select only has FROM metrics + """ + + return ( + select.from_ is not None + and len(select.from_.relations) == 1 + and len(select.from_.relations[0].extensions) == 0 + and str(select.from_.relations[0].primary) == "metrics" + ) + + +def resolve_metric_queries( # pylint: disable=R0914,R0912,R0915 + session: Session, + tree: ast.Query, + ctx: ast.CompileContext, + touched_nodes: Set[int], + metrics: List[Node], +): + """ + Find all Metric Node references + ensure they belong to a query sourcing `metrics` + build the queries as if they were API queries + replace them in the original query + """ + for col in tree.find_all(ast.Column): + curr_cols = [] + metric_nodes = [] + if id(col) in touched_nodes: + continue + ident = col.identifier(False) + if metric_node := try_get_dj_node(session, ident, {NodeType.METRIC}): + curr_cols.append((True, col)) + # if we found a metric node we need to check where it came from + parent_select = col.get_nearest_parent_of_type(ast.Select) + if not parent_select or not selects_only_metrics(parent_select): + raise ast.DJParseException( + "Any SELECT referencing a Metric must source " + "from a single unaliased Table named `metrics`.", + ) + metric_nodes.append(metric_node) + metrics.append(metric_node) + dimensions = [str(exp) for exp in parent_select.group_by] + + if any( + ( + parent_select.having, + parent_select.lateral_views, + parent_select.set_op, + ), + ): + raise ast.DJParseException( + "HAVING, LATERAL VIEWS, and SET OPERATIONS " + "are not allowed on `metrics` queries.", + ) + + for sibling_col in parent_select.projection: + if sibling_col is col: + continue + if not isinstance(sibling_col, ast.Column): + raise ast.DJParseException( + "Only direct Columns are allowed in " + f"`metrics` queries, found `{sibling_col}`.", + ) + + sibling_ident = sibling_col.identifier(False) + + sibling_node = try_get_dj_node( + session, + sibling_ident, + {NodeType.METRIC}, + ) + + if sibling_ident in dimensions: + curr_cols.append((False, sibling_col)) + elif sibling_node is not None: + curr_cols.append((True, sibling_col)) + else: + raise ast.DJParseException( + "You can only select direct METRIC nodes or a column " + f"from your GROUP BY on `metrics` queries, found `{sibling_col}`", + ) + + if sibling_node: + metric_nodes.append(sibling_node) + touched_nodes.add(id(sibling_col)) + + filters = [str(parent_select.where)] if parent_select.where else [] + + orderby = [ + str(sort) + for sort in ( + parent_select.organization.order + parent_select.organization.sort + if parent_select.organization + else [] + ) + ] + limit = None + if limit_exp := parent_select.limit: + try: + limit = int(str(limit_exp)) # type: ignore + except ValueError as exc: + raise ast.DJParseException( + f"LIMITs on `metrics` queries can only be integers not `{limit_exp}`.", + ) from exc + touched_nodes.add(id(col)) + + cte_name = ast.Name(f"metric_query_{len(tree.ctes)}") + + built = ( + build_metric_nodes( + session, + metric_nodes, + filters, + dimensions, + orderby, + limit, + ) + .bake_ctes() + .set_alias(cte_name) + .set_as(True) + ) + built.parenthesized = True + built.compile(ctx) + for built_col in built.columns: + built_col.alias_or_name.namespace = None + for is_metric, cur_col in curr_cols: + name = ( + amenable_name(cur_col.identifier(False)) + if is_metric + else cur_col.name.name + ) + + ref_type = [ + col for col in built.columns if col.alias_or_name.name == name + ][0].type + + swap_col = ( + ast.Column(ast.Name(name), _type=ref_type, _table=built) + .set_alias(cur_col.alias and cur_col.alias.copy()) + .set_as(True) + ) + cur_col.swap(swap_col) + + swap_select = ast.Select( + projection=parent_select.projection, + from_=ast.From( + relations=[ast.Relation(primary=ast.Table(built.alias_or_name))], + ), + ) + parent_select.swap(swap_select) + + tree.ctes = tree.ctes + [built] + touched_nodes.add(id(built)) + touched_nodes.add(id(swap_select)) + return tree + + +def find_all_other( + node: ast.Node, + touched_nodes: Set[int], + node_map: Dict[str, List[ast.Column]], +): + """ + Find all non-Metric DJ Node references + """ + if id(node) in touched_nodes: + return + touched_nodes.add(id(node)) + if isinstance(node, ast.Column) and node.table is None: + if namespace_name := node.name.namespace: + namespace = namespace_name.identifier(False) + if namespace in node_map: + node_map[namespace].append(node) + else: + node_map[namespace] = [node] + else: + node.apply(lambda n: find_all_other(n, touched_nodes, node_map)) + + +def resolve_all( # pylint: disable=R0914,W0640 + session: Session, + ctx: ast.CompileContext, + tree: ast.Query, + dj_nodes: List[Node], +): + """ + Resolve all references to DJ Nodes + """ + touched_nodes: Set[int] = set() + tree = resolve_metric_queries(session, tree, ctx, touched_nodes, dj_nodes) + node_map: Dict[str, List[ast.Column]] = {} + find_all_other(tree, touched_nodes, node_map) + for namespace, cols in node_map.items(): + if dj_node := try_get_dj_node( # pragma: no cover + session, + namespace, + {NodeType.SOURCE, NodeType.TRANSFORM, NodeType.DIMENSION}, + ): + dj_nodes.append(dj_node) + cte_name = ast.Name(f"node_query_{len(tree.ctes)}") + current_dj_node = dj_node.current + built = build_node(session, current_dj_node).bake_ctes() + built.alias = cte_name + built.set_as(True) + built.parenthesized = True + built.compile(ctx) + for cur_col in cols: + name = cur_col.name.name + ref_col = [col for col in current_dj_node.columns if col.name == name][ + 0 + ] + swap_col = ( + ast.Column(ast.Name(name), _table=built, _type=ref_col.type) + .set_alias(cur_col.alias and cur_col.alias.copy()) + .set_as(True) + ) + cur_col.swap(swap_col) + for tbl in tree.filter( + lambda node: isinstance(node, ast.Table) + and node.identifier(False) == dj_node.name, # type: ignore + ): + tbl.name = cte_name + tree.ctes = tree.ctes + [built] + built.parent = tree + return tree + + +def build_dj_query(session: Session, query: str) -> Tuple[ast.Query, List[Node]]: + """ + Build a sql query that refers to DJ Nodes + """ + dj_nodes: List[Node] = [] # metrics first if any + ctx = ast.CompileContext(session, ast.DJException()) + tree = parse(query).bake_ctes() + tree = resolve_all(session, ctx, tree, dj_nodes) + tree.compile(ctx) + if not dj_nodes: + raise ast.DJParseException(f"Found no dj nodes in query `{query}`.") + return tree, dj_nodes diff --git a/datajunction-server/datajunction_server/construction/exceptions.py b/datajunction-server/datajunction_server/construction/exceptions.py new file mode 100755 index 000000000..b276a39f3 --- /dev/null +++ b/datajunction-server/datajunction_server/construction/exceptions.py @@ -0,0 +1,57 @@ +""" +Exceptions used in construction +""" + +from typing import List, Optional + +from datajunction_server.errors import DJError, DJException + + +class CompoundBuildException: + """ + Exception singleton to optionally build up exceptions or raise + """ + + errors: List[DJError] + _instance: Optional["CompoundBuildException"] = None + _raise: bool = True + + def __new__(cls, *args, **kwargs): + if not cls._instance: + cls._instance = super(CompoundBuildException, cls).__new__( + cls, *args, **kwargs + ) + cls.errors = [] + return cls._instance + + def reset(self): + """ + Resets the singleton + """ + self._raise = True + self.errors = [] + + def set_raise(self, raise_: bool): + """ + Set whether to raise caught exceptions or accumulate them + """ + self._raise = raise_ + + def append(self, error: DJError, message: Optional[str] = None): + """ + Accumulate DJ exceptions + """ + if self._raise: + raise DJException( + message=message or error.message, + errors=[error], + ) + self.errors.append(error) + + def __str__(self) -> str: + plural = "s" if len(self.errors) > 1 else "" + error = f"Found {len(self.errors)} issue{plural}:\n" + return error + "\n\n".join( + "\t" + str(type(exc).__name__) + ": " + str(exc) + "\n" + "=" * 50 + for exc in self.errors + ) diff --git a/datajunction-server/datajunction_server/construction/utils.py b/datajunction-server/datajunction_server/construction/utils.py new file mode 100755 index 000000000..77bb3a06c --- /dev/null +++ b/datajunction-server/datajunction_server/construction/utils.py @@ -0,0 +1,60 @@ +""" +Utilities used around construction +""" + +from typing import TYPE_CHECKING, Optional, Set + +from sqlalchemy.orm.exc import NoResultFound +from sqlmodel import Session, select + +from datajunction_server.errors import DJError, DJErrorException, ErrorCode +from datajunction_server.models.node import Node, NodeRevision, NodeType + +if TYPE_CHECKING: + from datajunction_server.sql.parsing.ast import Name + + +def get_dj_node( + session: Session, + node_name: str, + kinds: Optional[Set[NodeType]] = None, + current: bool = True, +) -> NodeRevision: + """Return the DJ Node with a given name from a set of node types""" + query = select(Node).filter(Node.name == node_name) + if kinds: + query = query.filter(Node.type.in_(kinds)) # type: ignore # pylint: disable=no-member + match = None + try: + match = session.exec(query).one() + except NoResultFound as no_result_exc: + kind_msg = " or ".join(str(k) for k in kinds) if kinds else "" + raise DJErrorException( + DJError( + code=ErrorCode.UNKNOWN_NODE, + message=f"No node `{node_name}` exists of kind {kind_msg}.", + ), + ) from no_result_exc + return match.current if match and current else match + + +def to_namespaced_name(name: str) -> "Name": + """ + Builds a namespaced name from a string + """ + from datajunction_server.sql.parsing.ast import ( # pylint: disable=import-outside-toplevel + Name, + ) + + chunked = name.split(".") + chunked.reverse() + current_name = None + full_name = None + for chunk in chunked: + if not current_name: + current_name = Name(chunk) + full_name = current_name + else: + current_name.namespace = Name(chunk) + current_name = current_name.namespace + return full_name # type: ignore diff --git a/datajunction-server/datajunction_server/errors.py b/datajunction-server/datajunction_server/errors.py new file mode 100644 index 000000000..d1df99a26 --- /dev/null +++ b/datajunction-server/datajunction_server/errors.py @@ -0,0 +1,267 @@ +""" +Errors and warnings. +""" + +from enum import Enum +from typing import Any, Dict, List, Literal, Optional, TypedDict + +from sqlmodel import SQLModel + + +class ErrorCode(int, Enum): + """ + Error codes. + """ + + # generic errors + UNKNOWN_ERROR = 0 + NOT_IMPLEMENTED_ERROR = 1 + ALREADY_EXISTS = 2 + + # metric API + INVALID_FILTER_PATTERN = 100 + INVALID_COLUMN_IN_FILTER = 101 + INVALID_VALUE_IN_FILTER = 102 + + # SQL API + INVALID_ARGUMENTS_TO_FUNCTION = 200 + INVALID_SQL_QUERY = 201 + MISSING_COLUMNS = 202 + UNKNOWN_NODE = 203 + NODE_TYPE_ERROR = 204 + INVALID_DIMENSION_JOIN = 205 + INVALID_COLUMN = 206 + + # SQL Build Error + COMPOUND_BUILD_EXCEPTION = 300 + MISSING_PARENT = 301 + TYPE_INFERENCE = 302 + + # Auth + UNKNOWN_AUTHENTICATION_ERROR = 400 + OAUTH_ERROR = 401 + INVALID_LOGIN_CREDENTIALS = 402 + + +class DebugType(TypedDict, total=False): + """ + Type for debug information. + """ + + # link to where an issue can be filed + issue: str + + # link to documentation about the problem + documentation: str + + # any additional context + context: Dict[str, Any] + + +class DJErrorType(TypedDict): + """ + Type for serialized errors. + """ + + code: int + message: str + debug: Optional[DebugType] + + +class DJError(SQLModel): + """ + An error. + """ + + code: ErrorCode + message: str + debug: Optional[Dict[str, Any]] + context: str = "" + + def __str__(self) -> str: + """ + Format the error nicely. + """ + context = f" from `{self.context}`" if self.context else "" + return f"{self.message}{context} (error code: {self.code})" + + +class DJErrorException(Exception): + """ + Wrapper allows raising DJError + """ + + def __init__(self, dj_error: DJError): + self.dj_error = dj_error + + +class DJWarningType(TypedDict): + """ + Type for serialized warnings. + """ + + code: Optional[int] + message: str + debug: Optional[DebugType] + + +class DJWarning(SQLModel): + """ + A warning. + """ + + code: Optional[ErrorCode] = None + message: str + debug: Optional[Dict[str, Any]] + + +DBAPIExceptions = Literal[ + "Warning", + "Error", + "InterfaceError", + "DatabaseError", + "DataError", + "OperationalError", + "IntegrityError", + "InternalError", + "ProgrammingError", + "NotSupportedError", +] + + +class DJExceptionType(TypedDict): + """ + Type for serialized exceptions. + """ + + message: Optional[str] + errors: List[DJErrorType] + warnings: List[DJWarningType] + + +class DJException(Exception): + """ + Base class for errors. + """ + + message: str + errors: List[DJError] + warnings: List[DJWarning] + + # exception that should be raised when ``DJException`` is caught by the DB API cursor + dbapi_exception: DBAPIExceptions = "Error" + + # status code that should be returned when ``DJException`` is caught by the API layer + http_status_code: int = 500 + + def __init__( # pylint: disable=too-many-arguments + self, + message: Optional[str] = None, + errors: Optional[List[DJError]] = None, + warnings: Optional[List[DJWarning]] = None, + dbapi_exception: Optional[DBAPIExceptions] = None, + http_status_code: Optional[int] = None, + ): + self.errors = errors or [] + self.warnings = warnings or [] + self.message = message or "\n".join(error.message for error in self.errors) + + if dbapi_exception is not None: + self.dbapi_exception = dbapi_exception + if http_status_code is not None: + self.http_status_code = http_status_code + + super().__init__(self.message) + + def to_dict(self) -> DJExceptionType: + """ + Convert to dict. + """ + return { + "message": self.message, + "errors": [error.dict() for error in self.errors], + "warnings": [warning.dict() for warning in self.warnings], + } + + def __str__(self) -> str: + """ + Format the exception nicely. + """ + if not self.errors: + return self.message + + plural = "s" if len(self.errors) > 1 else "" + combined_errors = "\n".join(f"- {error}" for error in self.errors) + errors = f"The following error{plural} happened:\n{combined_errors}" + + return f"{self.message}\n{errors}" + + def __eq__(self, other) -> bool: + return ( + isinstance(other, DJException) + and self.message == other.message + and self.errors == other.errors + and self.warnings == other.warnings + and self.dbapi_exception == other.dbapi_exception + and self.http_status_code == other.http_status_code + ) + + +class DJNodeNotFound(DJException): + """ + Exception raised when a given node name is not found. + """ + + +class DJInvalidInputException(DJException): + """ + Exception raised when the input provided by the user is invalid. + """ + + dbapi_exception: DBAPIExceptions = "ProgrammingError" + http_status_code: int = 422 + + +class DJNotImplementedException(DJException): + """ + Exception raised when some functionality hasn't been implemented in DJ yet. + """ + + dbapi_exception: DBAPIExceptions = "NotSupportedError" + http_status_code: int = 500 + + +class DJInternalErrorException(DJException): + """ + Exception raised when we do something wrong in the code. + """ + + dbapi_exception: DBAPIExceptions = "InternalError" + http_status_code: int = 500 + + +class DJAlreadyExistsException(DJException): + """ + Exception raised when trying to create an entity that already exists. + """ + + dbapi_exception: DBAPIExceptions = "DataError" + http_status_code: int = 500 + + +class DJDoesNotExistException(DJException): + """ + Exception raised when an entity doesn't exist. + """ + + dbapi_exception: DBAPIExceptions = "DataError" + http_status_code: int = 404 + + +class DJQueryServiceClientException(DJException): + """ + Exception raised when the query service returns an error + """ + + dbapi_exception: DBAPIExceptions = "InterfaceError" + http_status_code: int = 500 diff --git a/datajunction-server/datajunction_server/internal/__init__.py b/datajunction-server/datajunction_server/internal/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/datajunction-server/datajunction_server/internal/authentication/__init__.py b/datajunction-server/datajunction_server/internal/authentication/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/datajunction-server/datajunction_server/internal/authentication/basic.py b/datajunction-server/datajunction_server/internal/authentication/basic.py new file mode 100644 index 000000000..56c2e76dc --- /dev/null +++ b/datajunction-server/datajunction_server/internal/authentication/basic.py @@ -0,0 +1,102 @@ +""" +Basic OAuth and JWT helper functions +""" +import logging +from http import HTTPStatus + +from fastapi import Depends, Request +from jose import JWTError +from passlib.context import CryptContext +from sqlalchemy.exc import NoResultFound +from sqlmodel import Session, select + +from datajunction_server.constants import UNAUTHENTICATED_ENDPOINTS +from datajunction_server.errors import DJError, DJException, ErrorCode +from datajunction_server.internal.authentication.jwt import decrypt, get_jwt +from datajunction_server.models import User +from datajunction_server.utils import get_session, get_settings + +_logger = logging.getLogger(__name__) +pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") + + +def verify_password(plain_password, hashed_password) -> bool: + """ + Verify a plain-text password against a hashed password + """ + return pwd_context.verify(plain_password, hashed_password) + + +def get_password_hash(password) -> str: + """ + Returns a hashed version of a plain-text password + """ + return pwd_context.hash(password) + + +def get_user_info(username: str, password: str, session: Session) -> User: + """ + Get a DJ user using basic auth + """ + user = session.exec(select(User).where(User.username == username)).one_or_none() + if not user or not verify_password(password, user.password): + raise DJException( + http_status_code=HTTPStatus.UNAUTHORIZED, + errors=[ + DJError( + message="Invalid username or password", + code=ErrorCode.INVALID_LOGIN_CREDENTIALS, + ), + ], + ) + return user + + +async def parse_basic_auth_cookie( + request: Request, + session: Session = Depends(get_session), +) -> None: + """ + Parse an "__dj" cookie for basic auth + """ + settings = get_settings() + github_oauth_configured = ( + all( + [ + settings.secret, + settings.github_oauth_client_id, + settings.github_oauth_client_secret, + ], + ) + or False + ) + _logger.info("Attempting to get basic authenticated user from request cookie") + jwt = None + try: + jwt = get_jwt(request=request) + except (JWTError, AttributeError): + pass + encrypted_username = jwt.get("sub") if jwt else None + username = decrypt(encrypted_username) if encrypted_username else None + user = None + try: + user = session.exec(select(User).where(User.username == username)).one() + except NoResultFound: + pass + if ( + not user + and not any([github_oauth_configured]) + and request.url.path not in UNAUTHENTICATED_ENDPOINTS + ): + # We must respond as unauthorized here if user is None + # because there are no more layers of auth middleware + raise DJException( + http_status_code=HTTPStatus.UNAUTHORIZED, + errors=[ + DJError( + code=ErrorCode.OAUTH_ERROR, + message="This endpoint requires authentication.", + ), + ], + ) + request.state.user = user diff --git a/datajunction-server/datajunction_server/internal/authentication/github.py b/datajunction-server/datajunction_server/internal/authentication/github.py new file mode 100644 index 000000000..50e1bed72 --- /dev/null +++ b/datajunction-server/datajunction_server/internal/authentication/github.py @@ -0,0 +1,115 @@ +""" +GitHub OAuth helper functions +""" +import logging +import secrets +from http import HTTPStatus +from typing import Optional +from urllib.parse import urljoin + +import requests +from fastapi import Request +from jose import JWTError +from sqlalchemy.exc import NoResultFound +from sqlmodel import select + +from datajunction_server.constants import UNAUTHENTICATED_ENDPOINTS +from datajunction_server.errors import DJError, DJException, ErrorCode +from datajunction_server.internal.authentication.basic import get_password_hash +from datajunction_server.internal.authentication.jwt import decrypt, get_jwt +from datajunction_server.models.user import OAuthProvider, User +from datajunction_server.utils import get_session, get_settings + +_logger = logging.getLogger(__name__) + + +def get_authorize_url(oauth_client_id: str) -> str: + """ + Get the authorize url for a GitHub OAuth app + """ + settings = get_settings() + redirect_uri = urljoin(settings.url, "/github/token/") + return ( + f"https://github.com/login/oauth/authorize?client_id={oauth_client_id}" + f"&scope=read:user&redirect_uri={redirect_uri}" + ) + + +def get_github_user(encrypted_access_token: str) -> Optional[User]: # pragma: no cover + """ + Get the user for a request + """ + access_token = decrypt(encrypted_access_token) + headers = {"Accept": "application/json", "Authorization": f"Bearer {access_token}"} + user_data = requests.get( + "https://api.github.com/user", + headers=headers, + timeout=10, + ).json() + if "message" in user_data and user_data["message"] == "Bad credentials": + return None + session = next(get_session()) + existing_user = None + try: + existing_user = session.exec( + select(User).where(User.username == user_data["login"]), + ).one() + except NoResultFound: + pass + if existing_user: + _logger.info("OAuth user found") + user = existing_user + else: + _logger.info("OAuth user does not exist, creating a new user") + new_user = User( + username=user_data["login"], + password=get_password_hash(secrets.token_urlsafe(13)), + email=user_data["email"], + name=user_data["name"], + oauth_provider=OAuthProvider.GITHUB, + ) + session.add(new_user) + session.commit() + session.refresh(new_user) + user = new_user + return user + + +async def parse_github_auth_cookie(request: Request) -> None: # pragma: no cover + """ + Middleware for parsing a "__dj" cookie for GitHub auth + """ + if not hasattr(request.state, "user") or not request.state.user: + _logger.info( + "Attempting to get GitHub authenticated user from request cookie", + ) + jwt = None + try: + jwt = get_jwt(request=request) + except (JWTError, AttributeError): + pass + encrypted_access_token = jwt.get("sub") if jwt else None + user = ( + get_github_user(encrypted_access_token=encrypted_access_token) + if encrypted_access_token + else None + ) + if not user: + if request.url.path not in UNAUTHENTICATED_ENDPOINTS: + # We must respond as unauthorized here if user is None + # because there are no more layers of auth middleware + raise DJException( + http_status_code=HTTPStatus.UNAUTHORIZED, + errors=[ + DJError( + code=ErrorCode.OAUTH_ERROR, + message="This endpoint requires authentication.", + ), + ], + ) + request.state.user = user + else: + _logger.info( + "GitHub authentication not checked, user already " + "set through a higher ranked auth scheme", + ) diff --git a/datajunction-server/datajunction_server/internal/authentication/jwt.py b/datajunction-server/datajunction_server/internal/authentication/jwt.py new file mode 100644 index 000000000..f4e93ed91 --- /dev/null +++ b/datajunction-server/datajunction_server/internal/authentication/jwt.py @@ -0,0 +1,70 @@ +"""JWT related functions""" + +from datetime import datetime, timedelta +from typing import Dict, Optional + +from fastapi import Request +from jose import jwe, jwt +from passlib.context import CryptContext + +from datajunction_server.utils import get_settings + +pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") + + +def create_jwt(data: dict, expires_delta: Optional[timedelta] = None) -> str: + """ + Return an encoded JSON web token for a dictionary + """ + settings = get_settings() + to_encode = data.copy() + if expires_delta: + expire = datetime.utcnow() + expires_delta + else: + expire = datetime.utcnow() + timedelta(minutes=15) + to_encode.update({"exp": expire}) + encoded_jwt = jwt.encode( + to_encode, + settings.secret, + algorithm="HS256", + ) + return encoded_jwt + + +def get_jwt(request: Request) -> Optional[Dict]: + """ + Get a DJ user from a request object by parsing the "__dj" cookie + + Raise: + JWTError: If the JWT token is malformed or the signature fails + AttributeError: If no "__dj" cookie is found on the request + """ + settings = get_settings() + token = request.cookies.get("__dj") + data = jwt.decode( + token, + settings.secret, + algorithms=["HS256"], + ) + return data + + +def encrypt(value: str) -> str: + """ + Encrypt a string value using the configured SECRET + """ + settings = get_settings() + return jwe.encrypt( + value, + settings.secret, + algorithm="dir", + encryption="A128GCM", + ).decode("utf-8") + + +def decrypt(value: str) -> str: + """ + Decrypt a string value using the configured SECRET + """ + settings = get_settings() + return jwe.decrypt(value, settings.secret).decode("utf-8") diff --git a/datajunction-server/datajunction_server/internal/materializations.py b/datajunction-server/datajunction_server/internal/materializations.py new file mode 100644 index 000000000..8f9068db9 --- /dev/null +++ b/datajunction-server/datajunction_server/internal/materializations.py @@ -0,0 +1,402 @@ +"""Node materialization helper functions""" +from typing import Dict, List, Set, Tuple, Union + +from pydantic import ValidationError +from sqlmodel import Session + +from datajunction_server.api.helpers import get_engine +from datajunction_server.construction.build import build_node +from datajunction_server.errors import DJInvalidInputException +from datajunction_server.materialization.jobs import ( + DefaultCubeMaterialization, + DruidCubeMaterializationJob, + MaterializationJob, + SparkSqlMaterializationJob, + TrinoMaterializationJob, +) +from datajunction_server.models import Engine, NodeRevision +from datajunction_server.models.engine import Dialect +from datajunction_server.models.materialization import ( + DruidConf, + DruidCubeConfig, + GenericCubeConfig, + GenericMaterializationConfig, + Materialization, + MaterializationInfo, + Measure, + MetricMeasures, + Partition, + PartitionType, + UpsertMaterialization, +) +from datajunction_server.models.node import NodeType +from datajunction_server.service_clients import QueryServiceClient +from datajunction_server.sql.parsing import ast + + +def build_cube_config( # pylint: disable=too-many-locals + cube_node: NodeRevision, + combined_ast: ast.Query, +) -> Union[DruidCubeConfig, GenericCubeConfig]: + """ + Builds the materialization config for a cube. This includes two parts: + (a) building a query that decomposes each of the cube's metrics into their + constituent measures that have simple aggregations + (b) adding a metric to measures mapping that tells us which measures + in the query map to each selected metric + + The query in the materialization config is different from the one stored + on the cube node itself in that this one is meant to create a temporary + table in preparation for ingestion into an OLAP database like Druid. The + metric to measures mapping then provides information on how to assemble + metrics from measures based on this query's output. + + The query directly on the cube node is meant for direct querying of the cube + without materialization to an OLAP database. + """ + dimensions_set = { + dim.name for dim in cube_node.columns if dim.has_dimension_attribute() + } + metrics_to_measures = {} + measures_tracker = {} + for cte in combined_ast.ctes: + metrics_to_measures.update(decompose_metrics(cte, dimensions_set)) + new_select_projection: Set[Union[ast.Aliasable, ast.Expression]] = set() + for expr in cte.select.projection: + if expr in metrics_to_measures: + combiner, measures = metrics_to_measures[expr] # type: ignore + new_select_projection = set(new_select_projection).union( + measures, + ) + metric_key = expr.alias_or_name.name # type: ignore + if metric_key not in measures_tracker: # pragma: no cover + measures_tracker[metric_key] = MetricMeasures( + metric=metric_key, + measures=[], + combiner=str(combiner), + ) + for measure in measures: + measures_tracker[metric_key].measures.append( # type: ignore + Measure( + name=measure.alias_or_name.name, + field_name=str( + f"{cte.alias_or_name}_{measure.alias_or_name}", + ), + agg=str(measure.child.name), + type=str(measure.type), + ), + ) + else: + new_select_projection.add(expr) + cte.select.projection = list(new_select_projection) + for _, metric_measures in measures_tracker.items(): + metric_measures.measures = sorted( + metric_measures.measures, + key=lambda x: x.name, + ) + combined_ast.select.projection = [ + ( + ast.Column(name=col.alias_or_name, _table=cte).set_alias( # type: ignore + ast.Name(f"{cte.alias_or_name}_{col.alias_or_name}"), # type: ignore + ) + ) + for cte in combined_ast.ctes + for col in cte.select.projection + if col.alias_or_name.name not in dimensions_set # type: ignore + ] + dimension_grouping: Dict[str, List[ast.Column]] = {} + for col in [ + ast.Column(name=col.alias_or_name, _table=cte) # type: ignore + for cte in combined_ast.ctes + for col in cte.select.projection + if col.alias_or_name.name in dimensions_set # type: ignore + ]: + dimension_grouping.setdefault(str(col.alias_or_name.name), []).append(col) + + for col_name, columns in dimension_grouping.items(): + combined_ast.select.projection.append( + ast.Function(name=ast.Name("COALESCE"), args=list(columns)).set_alias( + ast.Name(col_name), + ), + ) + + upstream_tables = sorted( + list( + { + f"{tbl.dj_node.catalog.name}.{tbl.identifier()}" + for tbl in combined_ast.find_all(ast.Table) + if tbl.dj_node + }, + ), + ) + return GenericCubeConfig( + query=str(combined_ast), + dimensions=sorted(list(dimensions_set)), + measures=measures_tracker, + partitions=[], + upstream_tables=upstream_tables, + ) + + +def materialization_job_from_engine(engine: Engine) -> MaterializationJob: + """ + Finds the appropriate materialization job based on the choice of engine. + """ + engine_to_job_mapping = { + Dialect.SPARK: SparkSqlMaterializationJob, + Dialect.TRINO: TrinoMaterializationJob, + Dialect.DRUID: DruidCubeMaterializationJob, + None: SparkSqlMaterializationJob, + } + if engine.dialect not in engine_to_job_mapping: + raise DJInvalidInputException( # pragma: no cover + f"The engine used for materialization ({engine.name}) " + "must have a dialect configured.", + ) + return engine_to_job_mapping[engine.dialect] # type: ignore + + +def filters_from_partitions(partitions: List[Partition]): + """ + Derive filters needed from partitions spec. + """ + filters = [] + for partition in partitions: + if partition.type_ != PartitionType.TEMPORAL: # pragma: no cover + if partition.values: # pragma: no cover + quoted_values = [f"'{value}'" for value in partition.values] + filters.append(f"{partition.name} IN ({','.join(quoted_values)})") + if partition.range and len(partition.range) == 2: + filters.append( # pragma: no cover + f"{partition.name} BETWEEN {partition.range[0]} " + f"AND {partition.range[1]}", + ) + return filters + + +def create_new_materialization( + session: Session, + current_revision: NodeRevision, + upsert: UpsertMaterialization, +) -> Materialization: + """ + Create a new materialization based on the input values. + """ + generic_config = None + engine = get_engine(session, upsert.engine.name, upsert.engine.version) + if current_revision.type in ( + NodeType.DIMENSION, + NodeType.TRANSFORM, + NodeType.METRIC, + ): + materialization_ast = build_node( + session=session, + node=current_revision, + filters=( + filters_from_partitions( + [ + Partition.parse_obj(partition) + for partition in upsert.config.partitions + ], + ) + if upsert.config.partitions + else [] + ), + dimensions=[], + orderby=[], + ) + generic_config = GenericMaterializationConfig( + query=str(materialization_ast), + spark=upsert.config.spark if upsert.config.spark else {}, + partitions=upsert.config.partitions if upsert.config.partitions else [], + upstream_tables=[ + f"{current_revision.catalog.name}.{tbl.identifier()}" + for tbl in materialization_ast.find_all(ast.Table) + ], + ) + + if current_revision.type == NodeType.CUBE: + # Check to see if a default materialization was already configured, so that we + # can copy over the default cube setup and layer on specific config as needed + default_job = [ + conf + for conf in current_revision.materializations + if conf.job == DefaultCubeMaterialization.__name__ + ][0] + default_job_config = GenericCubeConfig.parse_obj(default_job.config) + try: + generic_config = DruidCubeConfig( + node_name=current_revision.name, + query=default_job_config.query, + dimensions=default_job_config.dimensions, + measures=default_job_config.measures, + spark=upsert.config.spark, + druid=DruidConf.parse_obj(upsert.config.druid), + partitions=upsert.config.partitions, + upstream_tables=default_job_config.upstream_tables, + ) + except (KeyError, ValidationError, AttributeError) as exc: + raise DJInvalidInputException( + message=( + "No change has been made to the materialization config for " + f"node `{current_revision.name}` and engine `{engine.name}` as" + " the config does not have valid configuration for " + f"engine `{engine.name}`." + ), + ) from exc + materialization_name = generic_config.identifier() # type: ignore + return Materialization( + name=materialization_name, + node_revision=current_revision, + engine=engine, + config=generic_config, + schedule=upsert.schedule or "@daily", + job=materialization_job_from_engine(engine).__name__, # type: ignore + ) + + +def schedule_materialization_jobs( + materializations: List[Materialization], + query_service_client: QueryServiceClient, +) -> Dict[str, MaterializationInfo]: + """ + Schedule recurring materialization jobs + """ + materialization_jobs = { + cls.__name__: cls for cls in MaterializationJob.__subclasses__() + } + materialization_to_output = {} + for materialization in materializations: + clazz = materialization_jobs.get(materialization.job) + if clazz and materialization.name: # pragma: no cover + materialization_to_output[materialization.name] = clazz().schedule( # type: ignore + materialization, + query_service_client, + ) + return materialization_to_output + + +def _get_readable_name(expr): + """ + Returns a readable name based on the columns in the expression. This is used + if we want to represent the expression as a single measure, which needs a name + """ + columns = [col for arg in expr.args for col in arg.find_all(ast.Column)] + return ( + "_".join(str(col.alias_or_name).rsplit(".", maxsplit=1)[-1] for col in columns) + if columns + else "placeholder" + ) + + +def decompose_expression( # pylint: disable=too-many-return-statements + expr: Union[ast.Aliasable, ast.Expression], +) -> Tuple[ast.Expression, List[ast.Alias]]: + """ + Takes a metric expression and (a) determines the measures needed to evaluate + the metric and (b) includes the query expression needed to recombine these + measures into the metric, given a materialized cube. + + Simple aggregations are operations that can be computed incrementally as new + data is ingested, without relying on the results of other aggregations. + Examples include SUM, COUNT, MIN, MAX. + + Some complex aggregations can be decomposed to simple aggregations: i.e., AVG(x) can + be decomposed to SUM(x)/COUNT(x). + """ + if isinstance(expr, ast.Alias): + expr = expr.child + + if isinstance(expr, ast.Number): + return expr, [] # type: ignore + + if not expr.is_aggregation(): # type: ignore # pragma: no cover + return expr, [expr] # type: ignore + + simple_aggregations = {"sum", "count", "min", "max"} + if isinstance(expr, ast.Function): + function_name = expr.alias_or_name.name.lower() + readable_name = _get_readable_name(expr) + + if function_name in simple_aggregations: + measure_name = ast.Name(f"{readable_name}_{function_name}") + if not expr.args[0].is_aggregation(): + combiner: ast.Expression = ast.Function( + name=ast.Name(function_name), + args=[ast.Column(name=measure_name)], + ) + return combiner, [expr.set_alias(measure_name)] + + combiner, measures = decompose_expression(expr.args[0]) + return ( + ast.Function( + name=ast.Name(function_name), + args=[combiner], + ), + measures, + ) + + if function_name == "avg": # pragma: no cover + numerator_measure_name = ast.Name(f"{readable_name}_sum") + denominator_measure_name = ast.Name(f"{readable_name}_count") + combiner = ast.BinaryOp( + left=ast.Function( + ast.Name("sum"), + args=[ast.Column(name=numerator_measure_name)], + ), + right=ast.Function( + ast.Name("count"), + args=[ast.Column(name=denominator_measure_name)], + ), + op=ast.BinaryOpKind.Divide, + ) + return combiner, [ + ( + ast.Function(ast.Name("sum"), args=expr.args).set_alias( + numerator_measure_name, + ) + ), + ( + ast.Function(ast.Name("count"), args=expr.args).set_alias( + denominator_measure_name, + ) + ), + ] + acceptable_binary_ops = { + ast.BinaryOpKind.Plus, + ast.BinaryOpKind.Minus, + ast.BinaryOpKind.Multiply, + ast.BinaryOpKind.Divide, + } + if isinstance(expr, ast.BinaryOp): + if expr.op in acceptable_binary_ops: # pragma: no cover + measures_combiner_left, measures_left = decompose_expression(expr.left) + measures_combiner_right, measures_right = decompose_expression(expr.right) + combiner = ast.BinaryOp( + left=measures_combiner_left, + right=measures_combiner_right, + op=expr.op, + ) + return combiner, measures_left + measures_right + + if isinstance(expr, ast.Cast): + return decompose_expression(expr.expression) + + raise DJInvalidInputException( # pragma: no cover + f"Metric expression {expr} cannot be decomposed into its constituent measures", + ) + + +def decompose_metrics( + combined_ast: ast.Query, + dimensions_set: Set[str], +) -> Dict[Union[ast.Aliasable, ast.Expression], Tuple[ast.Expression, List[ast.Alias]]]: + """ + Decompose each metric into simple constituent measures and return a dict + that maps each metric to its measures. + """ + metrics_to_measures = {} + for expr in combined_ast.select.projection: + if expr.alias_or_name.name not in dimensions_set: # type: ignore + metrics_to_measures[expr] = decompose_expression(expr) + return metrics_to_measures diff --git a/datajunction-server/datajunction_server/internal/namespaces.py b/datajunction-server/datajunction_server/internal/namespaces.py new file mode 100644 index 000000000..315724fe4 --- /dev/null +++ b/datajunction-server/datajunction_server/internal/namespaces.py @@ -0,0 +1,111 @@ +""" +Helper methods for namespaces endpoints. +""" +from datetime import datetime +from typing import List + +from sqlalchemy import and_ +from sqlalchemy.sql.operators import is_ +from sqlmodel import Session, select + +from datajunction_server.models import History +from datajunction_server.models.history import ActivityType, EntityType +from datajunction_server.models.node import Node, NodeNamespace, NodeType +from datajunction_server.typing import UTCDatetime + + +def get_nodes_in_namespace( + session: Session, + namespace: str, + node_type: NodeType = None, + include_deactivated: bool = False, +) -> List[str]: + """ + Gets a list of node names in the namespace + """ + where_clause = ( + and_( + Node.namespace.like( # type: ignore # pylint: disable=no-member + f"{namespace}%", + ), + Node.type == node_type, + ) + if node_type + else Node.namespace.like( # type: ignore # pylint: disable=no-member + f"{namespace}%", + ) + ) + + list_nodes_query = select(Node.name).where( + where_clause, + ) # .where(is_(Node.deactivated_at, None)) + if include_deactivated is False: + list_nodes_query = list_nodes_query.where(is_(Node.deactivated_at, None)) + return session.exec(list_nodes_query).all() + + +def mark_namespace_deactivated( + session: Session, + namespace: NodeNamespace, + message: str = None, +): + """ + Deactivates the node namespace and updates history indicating so + """ + now = datetime.utcnow() + namespace.deactivated_at = UTCDatetime( + year=now.year, + month=now.month, + day=now.day, + hour=now.hour, + minute=now.minute, + second=now.second, + ) + session.add( + History( + entity_type=EntityType.NAMESPACE, + entity_name=namespace.namespace, + node=None, + activity_type=ActivityType.DELETE, + details={"message": message or ""}, + ), + ) + session.commit() + + +def mark_namespace_restored( + session: Session, + namespace: NodeNamespace, + message: str = None, +): + """ + Restores the node namespace and updates history indicating so + """ + namespace.deactivated_at = None # type: ignore + session.add( + History( + entity_type=EntityType.NAMESPACE, + entity_name=namespace.namespace, + node=None, + activity_type=ActivityType.RESTORE, + details={"message": message or ""}, + ), + ) + session.commit() + + +def create_namespace(session: Session, namespace: str): + """ + Creates a namespace entry in the database table. + """ + node_namespace = NodeNamespace(namespace=namespace) + session.add(node_namespace) + session.add( + History( + entity_type=EntityType.NAMESPACE, + entity_name=namespace, + node=None, + activity_type=ActivityType.CREATE, + ), + ) + session.commit() diff --git a/datajunction-server/datajunction_server/internal/nodes.py b/datajunction-server/datajunction_server/internal/nodes.py new file mode 100644 index 000000000..484c97060 --- /dev/null +++ b/datajunction-server/datajunction_server/internal/nodes.py @@ -0,0 +1,765 @@ +"""Nodes endpoint helper functions""" +import logging +from collections import defaultdict +from http import HTTPStatus +from typing import List, Optional + +from cachetools import LRUCache, cached # type: ignore +from cachetools.keys import hashkey # type: ignore +from fastapi import Depends +from sqlmodel import Session, select + +from datajunction_server.api.helpers import ( + activate_node, + get_attribute_type, + get_engine, + get_node_by_name, + propagate_valid_status, + resolve_downstream_references, + validate_cube, + validate_node_data, +) +from datajunction_server.construction.build import build_metric_nodes +from datajunction_server.errors import DJDoesNotExistException, DJException +from datajunction_server.internal.materializations import ( + build_cube_config, + create_new_materialization, + schedule_materialization_jobs, +) +from datajunction_server.materialization.jobs import ( + DefaultCubeMaterialization, + DruidCubeMaterializationJob, +) +from datajunction_server.models import ( + AttributeType, + Column, + ColumnAttribute, + History, + Node, + NodeRevision, +) +from datajunction_server.models.attribute import UniquenessScope +from datajunction_server.models.base import generate_display_name +from datajunction_server.models.column import ColumnAttributeInput +from datajunction_server.models.history import ( + ActivityType, + EntityType, + status_change_history, +) +from datajunction_server.models.materialization import ( + DruidCubeConfig, + Materialization, + UpsertMaterialization, +) +from datajunction_server.models.node import ( + DEFAULT_DRAFT_VERSION, + DEFAULT_PUBLISHED_VERSION, + CreateCubeNode, + CreateNode, + CreateSourceNode, + LineageColumn, + MissingParent, + NodeMode, + NodeStatus, + NodeType, + UpdateNode, +) +from datajunction_server.service_clients import QueryServiceClient +from datajunction_server.sql.parsing import ast +from datajunction_server.sql.parsing.ast import CompileContext +from datajunction_server.sql.parsing.backends.antlr4 import parse +from datajunction_server.sql.parsing.backends.exceptions import DJParseException +from datajunction_server.utils import ( + Version, + VersionUpgrade, + get_query_service_client, + get_session, +) + +_logger = logging.getLogger(__name__) + + +def validate_and_build_attribute( + session: Session, + attribute_input: ColumnAttributeInput, + node: Node, +) -> ColumnAttribute: + """ + Run some validation and build column attribute. + """ + column_map = {column.name: column for column in node.current.columns} + if attribute_input.column_name not in column_map: + raise DJDoesNotExistException( + message=f"Column `{attribute_input.column_name}` " + f"does not exist on node `{node.name}`!", + ) + column = column_map[attribute_input.column_name] + existing_attributes = {attr.attribute_type.name: attr for attr in column.attributes} + if attribute_input.attribute_type_name in existing_attributes: + return existing_attributes[attribute_input.attribute_type_name] + + # Verify attribute type exists + attribute_type = get_attribute_type( + session, + attribute_input.attribute_type_name, + attribute_input.attribute_type_namespace, + ) + if not attribute_type: + raise DJDoesNotExistException( + message=f"Attribute type `{attribute_input.attribute_type_namespace}" + f".{attribute_input.attribute_type_name}` " + f"does not exist!", + ) + + # Verify that the attribute type is allowed for this node + if node.type not in attribute_type.allowed_node_types: + raise DJException( + message=f"Attribute type `{attribute_input.attribute_type_namespace}." + f"{attribute_type.name}` not allowed on node " + f"type `{node.type}`!", + ) + + return ColumnAttribute( + attribute_type=attribute_type, + column=column, + ) + + +def set_column_attributes_on_node( + session: Session, + attributes: List[ColumnAttributeInput], + node: Node, +) -> List[Column]: + """ + Sets the column attributes on the node if allowed. + """ + modified_columns_map = {} + for attribute_input in attributes: + new_attribute = validate_and_build_attribute(session, attribute_input, node) + # pylint: disable=no-member + modified_columns_map[new_attribute.column.name] = new_attribute.column + + # Validate column attributes by building mapping between + # attribute scope and columns + attributes_columns_map = defaultdict(set) + modified_columns = modified_columns_map.values() + + for column in modified_columns: + for attribute in column.attributes: + scopes_map = { + UniquenessScope.NODE: attribute.attribute_type, + UniquenessScope.COLUMN_TYPE: column.type, + } + attributes_columns_map[ + ( # type: ignore + attribute.attribute_type, + tuple( + scopes_map[item] + for item in attribute.attribute_type.uniqueness_scope + ), + ) + ].add(column.name) + + for (attribute, _), columns in attributes_columns_map.items(): + if len(columns) > 1 and attribute.uniqueness_scope: + for col in columns: + modified_columns_map[col].attributes = [] + raise DJException( + message=f"The column attribute `{attribute.name}` is scoped to be " + f"unique to the `{attribute.uniqueness_scope}` level, but there " + "is more than one column tagged with it: " + f"`{', '.join(sorted(list(columns)))}`", + ) + + session.add_all(modified_columns) + session.add( + History( + entity_type=EntityType.COLUMN_ATTRIBUTE, + node=node.name, + activity_type=ActivityType.SET_ATTRIBUTE, + details={ + "attributes": [attr.dict() for attr in attributes], + }, + ), + ) + session.commit() + for col in modified_columns: + session.refresh(col) + + session.refresh(node) + session.refresh(node.current) + return list(modified_columns) + + +def create_node_revision( + data: CreateNode, + node_type: NodeType, + session: Session, +) -> NodeRevision: + """ + Create a non-source node revision. + """ + node_revision = NodeRevision( + name=data.name, + namespace=data.namespace, + display_name=data.display_name + if data.display_name + else generate_display_name(data.name), + description=data.description, + type=node_type, + status=NodeStatus.VALID, + query=data.query, + mode=data.mode, + required_dimensions=data.required_dimensions or [], + ) + ( + validated_node, + dependencies_map, + missing_parents_map, + _, + errors, + ) = validate_node_data(node_revision, session) + if errors: + if node_revision.mode == NodeMode.DRAFT: + node_revision.status = NodeStatus.INVALID + else: + raise DJException( + http_status_code=HTTPStatus.BAD_REQUEST, + errors=errors, + ) + else: + node_revision.status = NodeStatus.VALID + node_revision.missing_parents = [ + MissingParent(name=missing_parent) for missing_parent in missing_parents_map + ] + new_parents = [node.name for node in dependencies_map] + catalog_ids = [node.catalog_id for node in dependencies_map] + if node_revision.mode == NodeMode.PUBLISHED and not len(set(catalog_ids)) <= 1: + raise DJException( + f"Cannot create nodes with multi-catalog dependencies: {set(catalog_ids)}", + ) + catalog_id = next(iter(catalog_ids), 0) + parent_refs = session.exec( + select(Node).where( + # pylint: disable=no-member + Node.name.in_( # type: ignore + new_parents, + ), + ), + ).all() + node_revision.parents = parent_refs + + _logger.info( + "Parent nodes for %s (%s): %s", + data.name, + node_revision.version, + [p.name for p in node_revision.parents], + ) + node_revision.columns = validated_node.columns or [] + node_revision.catalog_id = catalog_id + return node_revision + + +def create_cube_node_revision( # pylint: disable=too-many-locals + session: Session, + data: CreateCubeNode, +) -> NodeRevision: + """ + Create a cube node revision. + """ + ( + metric_columns, + metric_nodes, + dimension_nodes, + dimension_columns, + catalog, + ) = validate_cube( + session, + data.metrics, + data.dimensions, + ) + + combined_ast = build_metric_nodes( + session, + metric_nodes, + filters=data.filters or [], + dimensions=data.dimensions or [], + orderby=data.orderby or [], + limit=data.limit or None, + ) + dimension_attribute = session.exec( + select(AttributeType).where(AttributeType.name == "dimension"), + ).one() + dimensions_set = {dim.rsplit(".", 1)[1] for dim in data.dimensions} + + node_columns = [] + status = NodeStatus.VALID + type_inference_failed_columns = [] + for col in combined_ast.select.projection: + try: + column_type = col.type # type: ignore + column_attributes = ( + [ColumnAttribute(attribute_type=dimension_attribute)] + if col.alias_or_name.name in dimensions_set + else [] + ) + node_columns.append( + Column( + name=col.alias_or_name.name, + type=column_type, + attributes=column_attributes, + ), + ) + except DJParseException: # pragma: no cover + type_inference_failed_columns.append(col.alias_or_name.name) # type: ignore + status = NodeStatus.INVALID + + node_revision = NodeRevision( + name=data.name, + namespace=data.namespace, + description=data.description, + type=NodeType.CUBE, + query=str(combined_ast), + columns=node_columns, + cube_elements=metric_columns + dimension_columns, + parents=list(set(dimension_nodes + metric_nodes)), + status=status, + catalog=catalog, + ) + + # Set up a default materialization for the cube. Note that this does not get used + # for any actual materialization, but is for storing info needed for materialization + node_revision.materializations = [] + default_materialization = UpsertMaterialization( + name="placeholder", + engine=node_revision.catalog.engines[0], # pylint: disable=no-member + schedule="@daily", + config={}, + job="CubeMaterializationJob", + ) + engine = get_engine( + session, + name=default_materialization.engine.name, + version=default_materialization.engine.version, + ) + cube_custom_config = build_cube_config( + node_revision, + combined_ast, + ) + new_materialization = Materialization( + name=cube_custom_config.identifier(), + node_revision=node_revision, + engine=engine, + config=cube_custom_config, + schedule=default_materialization.schedule, + job=( + DefaultCubeMaterialization.__name__ + if not isinstance(cube_custom_config, DruidCubeConfig) + else DruidCubeMaterializationJob.__name__ + ), + ) + node_revision.materializations.append(new_materialization) + return node_revision + + +def save_node( + session: Session, + node_revision: NodeRevision, + node: Node, + node_mode: NodeMode, +): + """ + Links the node and node revision together and saves them + """ + node_revision.node = node + node_revision.version = ( + str(DEFAULT_DRAFT_VERSION) + if node_mode == NodeMode.DRAFT + else str(DEFAULT_PUBLISHED_VERSION) + ) + node.current_version = node_revision.version + node_revision.extra_validation() + + session.add(node) + session.add( + History( + node=node.name, + entity_type=EntityType.NODE, + entity_name=node.name, + activity_type=ActivityType.CREATE, + ), + ) + session.commit() + + newly_valid_nodes = resolve_downstream_references( + session=session, + node_revision=node_revision, + ) + propagate_valid_status( + session=session, + valid_nodes=newly_valid_nodes, + catalog_id=node.current.catalog_id, # pylint: disable=no-member + ) + session.refresh(node.current) + + +def _update_node( + name: str, + data: UpdateNode, + session: Session, + query_service_client: QueryServiceClient = Depends(get_query_service_client), +): + query = ( + select(Node) + .where(Node.name == name) + .with_for_update() + .execution_options(populate_existing=True) + ) + node = session.exec(query).one_or_none() + if not node: + raise DJException( + message=f"A node with name `{name}` does not exist.", + http_status_code=404, + ) + + old_revision = node.current + new_revision = create_new_revision_from_existing( + session, + query_service_client, + old_revision, + node, + data, + ) + + if not new_revision: + return node # type: ignore + + node.current_version = new_revision.version + + new_revision.extra_validation() + + session.add(new_revision) + session.add(node) + + session.add( + History( + entity_type=EntityType.NODE, + entity_name=node.name, + node=node.name, + activity_type=ActivityType.UPDATE, + details={ + "version": new_revision.version, + }, + ), + ) + + if new_revision.status != old_revision.status: + session.add( + status_change_history( + new_revision, + old_revision.status, + new_revision.status, + ), + ) + session.commit() + session.refresh(node.current) + return node + + +def _create_node_from_inactive( + new_node_type: NodeType, + data: CreateSourceNode, + session: Session = Depends(get_session), +) -> Optional[Node]: + """ + If the node existed and is inactive the re-creation takes different steps than + creating it from scratch. + """ + previous_inactive_node = get_node_by_name( + session, + name=data.name, + raise_if_not_exists=False, + include_inactive=True, + ) + if previous_inactive_node and previous_inactive_node.deactivated_at: + if previous_inactive_node.type != new_node_type: + raise DJException( # pragma: no cover + message=f"A node with name `{data.name}` of a `{previous_inactive_node.type.value}` " # pylint: disable=line-too-long + "type existed before. If you want to re-created with a different type now, " + "you need to remove all the traces of the previous node with a command.", + http_status_code=HTTPStatus.CONFLICT, + ) + _update_node( + name=data.name, + data=UpdateNode( + # MutableNodeFields + display_name=data.display_name, + description=data.description, + mode=data.mode, + # SourceNodeFields + catalog=data.catalog, + schema_=data.schema_, + table=data.table, + columns=data.columns, + ), + session=session, + ) + try: + activate_node(name=data.name, session=session) + return get_node_by_name(session, data.name, with_current=True) + except Exception as exc: # pragma: no cover + raise DJException( + f"Restoring node `{data.name}` failed: {exc}", + ) from exc + + return None + + +def create_new_revision_from_existing( # pylint: disable=too-many-locals,too-many-arguments,too-many-branches + session: Session, + query_service_client: QueryServiceClient, + old_revision: NodeRevision, + node: Node, + data: UpdateNode = None, + version_upgrade: VersionUpgrade = None, +) -> Optional[NodeRevision]: + """ + Creates a new revision from an existing node revision. + """ + minor_changes = ( + (data and data.description and old_revision.description != data.description) + or (data and data.mode and old_revision.mode != data.mode) + or ( + data + and data.display_name + and old_revision.display_name != data.display_name + ) + ) + + if node.type == NodeType.METRIC: + data.query = NodeRevision.format_metric_alias(data.query, node.name) # type: ignore + + query_changes = ( + old_revision.type != NodeType.SOURCE + and data + and data.query + and old_revision.query != data.query + ) + column_changes = ( + old_revision.type == NodeType.SOURCE + and data is not None + and data.columns is not None + and ({col.identifier() for col in old_revision.columns} != data.columns) + ) + pk_changes = ( + data is not None + and data.primary_key + and {col.name for col in old_revision.primary_key()} != set(data.primary_key) + ) + major_changes = query_changes or column_changes or pk_changes + + # If nothing has changed, do not create the new node revision + if not minor_changes and not major_changes and not version_upgrade: + return None + + old_version = Version.parse(node.current_version) + new_mode = data.mode if data and data.mode else old_revision.mode + new_revision = NodeRevision( + name=old_revision.name, + node_id=node.id, + version=str( + old_version.next_major_version() + if major_changes or version_upgrade == VersionUpgrade.MAJOR + else old_version.next_minor_version(), + ), + display_name=( + data.display_name + if data and data.display_name + else old_revision.display_name + ), + description=( + data.description if data and data.description else old_revision.description + ), + query=(data.query if data and data.query else old_revision.query), + type=old_revision.type, + columns=[ + Column( + name=column_data.name, + type=column_data.type, + dimension_column=column_data.dimension, + attributes=column_data.attributes or [], + ) + for column_data in data.columns + ] + if data and data.columns + else old_revision.columns, + catalog=old_revision.catalog, + schema_=old_revision.schema_, + table=old_revision.table, + parents=[], + mode=new_mode, + materializations=[], + status=old_revision.status, + ) + + # Link the new revision to its parents if the query has changed and update its status + if new_revision.type != NodeType.SOURCE and (query_changes or pk_changes): + ( + validated_node, + dependencies_map, + missing_parents_map, + _, + errors, + ) = validate_node_data(new_revision, session) + + if errors: + if new_mode == NodeMode.DRAFT: + new_revision.status = NodeStatus.INVALID + else: + raise DJException( + http_status_code=HTTPStatus.BAD_REQUEST, + errors=errors, + ) + + # Keep the dimension links and attributes on the columns from the node's + # last revision if any existed + old_columns_mapping = {col.name: col for col in old_revision.columns} + for col in validated_node.columns: + if col.name in old_columns_mapping: + col.dimension_id = old_columns_mapping[col.name].dimension_id + col.attributes = old_columns_mapping[col.name].attributes or [] + + new_parents = [n.name for n in dependencies_map] + parent_refs = session.exec( + select(Node).where( + # pylint: disable=no-member + Node.name.in_( # type: ignore + new_parents, + ), + ), + ).all() + new_revision.parents = list(parent_refs) + new_revision.columns = validated_node.columns or [] + + # Update the primary key if one was set in the input + if data is not None and data.primary_key: + pk_attribute = session.exec( + select(AttributeType).where(AttributeType.name == "primary_key"), + ).one() + for col in new_revision.columns: + if col.name in data.primary_key and not col.has_primary_key_attribute(): + col.attributes.append( + ColumnAttribute(column=col, attribute_type=pk_attribute), + ) + + # Set the node's validity status + invalid_primary_key = ( + new_revision.type == NodeType.DIMENSION and not new_revision.primary_key() + ) + if invalid_primary_key: + new_revision.status = NodeStatus.INVALID + + new_revision.missing_parents = [ + MissingParent(name=missing_parent) for missing_parent in missing_parents_map + ] + _logger.info( + "Parent nodes for %s (v%s): %s", + new_revision.name, + new_revision.version, + [p.name for p in new_revision.parents], + ) + new_revision.columns = validated_node.columns or [] + + # Handle materializations + active_materializations = [ + mat for mat in old_revision.materializations if not mat.deactivated_at + ] + if active_materializations and query_changes: + for old in active_materializations: + new_revision.materializations.append( + create_new_materialization( + session, + new_revision, + UpsertMaterialization( + **old.dict(), **{"engine": old.engine.dict()} + ), + ), + ) + schedule_materialization_jobs( + new_revision.materializations, + query_service_client, + ) + return new_revision + + +@cached( + cache=LRUCache(maxsize=1000), + key=lambda session, node_rev, column_name: hashkey(node_rev, column_name), +) +def column_level_lineage( + session: Session, + node_rev: NodeRevision, + column_name: str, +) -> LineageColumn: + """ + Determines the column-level lineage for a column on a node. + """ + if node_rev.type == NodeType.SOURCE: + return LineageColumn( + node_name=node_rev.name, + node_type=node_rev.type, + display_name=node_rev.display_name, + column_name=column_name, + lineage=[], + ) + + ctx = CompileContext(session, DJException()) + query_ast = parse(node_rev.query) + query_ast.compile(ctx) + + lineage_column = LineageColumn( + column_name=column_name, + node_name=node_rev.name, + node_type=node_rev.type, + display_name=node_rev.display_name, + lineage=[], + ) + + # Find the expression AST for the column on the node + column = [ + col + for col in query_ast.select.projection + if ( # pragma: no cover + col != ast.Null() and col.alias_or_name.name == column_name # type: ignore + ) + ][0] + column_or_child = column.child if isinstance(column, ast.Alias) else column # type: ignore + column_expr = ( + column_or_child.expression # type: ignore + if hasattr(column_or_child, "expression") + else column_or_child + ) + + # At every layer, expand the lineage search tree with all columns referenced + # by the current column's expression. If we reach an actual table with a DJ + # node attached, save this to the lineage record. Otherwise, continue the search + processed = list(column_expr.find_all(ast.Column)) + while processed: + current = processed.pop() + if hasattr(current, "table") and isinstance(current.table, ast.Table): + lineage_column.lineage.append( # type: ignore + column_level_lineage( + session, + current.table.dj_node, + current.name.name + if not current.is_struct_ref + else current.struct_column_name, + ), + ) + else: + expr_column_deps = list( + current.expression.find_all(ast.Column), + ) + for col_dep in expr_column_deps: + processed.append(col_dep) + return lineage_column diff --git a/datajunction-server/datajunction_server/materialization/__init__.py b/datajunction-server/datajunction_server/materialization/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/datajunction-server/datajunction_server/materialization/jobs/__init__.py b/datajunction-server/datajunction_server/materialization/jobs/__init__.py new file mode 100644 index 000000000..7cfa962e5 --- /dev/null +++ b/datajunction-server/datajunction_server/materialization/jobs/__init__.py @@ -0,0 +1,19 @@ +""" +Available materialization jobs. +""" +__all__ = [ + "MaterializationJob", + "SparkSqlMaterializationJob", + "TrinoMaterializationJob", + "DefaultCubeMaterialization", + "DruidCubeMaterializationJob", +] +from datajunction_server.materialization.jobs.cube_materialization import ( + DefaultCubeMaterialization, + DruidCubeMaterializationJob, +) +from datajunction_server.materialization.jobs.materialization_job import ( + MaterializationJob, + SparkSqlMaterializationJob, + TrinoMaterializationJob, +) diff --git a/datajunction-server/datajunction_server/materialization/jobs/cube_materialization.py b/datajunction-server/datajunction_server/materialization/jobs/cube_materialization.py new file mode 100644 index 000000000..d15e2cb90 --- /dev/null +++ b/datajunction-server/datajunction_server/materialization/jobs/cube_materialization.py @@ -0,0 +1,156 @@ +""" +Cube materialization jobs +""" +from typing import Dict + +from datajunction_server.errors import DJException +from datajunction_server.materialization.jobs.materialization_job import ( + MaterializationJob, +) +from datajunction_server.models.engine import Dialect +from datajunction_server.models.materialization import ( + DruidCubeConfig, + DruidMaterializationInput, + Materialization, + MaterializationInfo, + PartitionType, +) +from datajunction_server.service_clients import QueryServiceClient + +DRUID_AGG_MAPPING = { + ("bigint", "sum"): "longSum", + ("double", "sum"): "doubleSum", + ("float", "sum"): "floatSum", + ("double", "min"): "doubleMin", + ("double", "max"): "doubleMax", + ("float", "min"): "floatMin", + ("float", "max"): "floatMax", + ("bigint", "min"): "longMin", + ("bigint", "max"): "longMax", + ("bigint", "count"): "longSum", + ("double", "count"): "longSum", + ("float", "count"): "longSum", +} + + +class DefaultCubeMaterialization( + MaterializationJob, +): # pylint: disable=too-few-public-methods + """ + Dummy job that is not meant to be executed but contains all the + settings needed for to materialize a generic cube. + """ + + def schedule( + self, + materialization: Materialization, + query_service_client: QueryServiceClient, + ): + """ + Since this is a settings-only dummy job, we do nothing in this stage. + """ + return # pragma: no cover + + +class DruidCubeMaterializationJob(MaterializationJob): + """ + Druid materialization for a cube node. + """ + + dialect = Dialect.DRUID + + def build_druid_spec(self, cube_config: DruidCubeConfig, node_name: str) -> Dict: + """ + Builds the Druid ingestion spec from a materialization config. + """ + if not cube_config.druid: # pragma: no cover + raise DJException("Druid ingestion requires a druid spec") + + druid_datasource_name = ( + cube_config.prefix # type: ignore + + node_name.replace(".", "_DOT_") # type: ignore + + cube_config.suffix # type: ignore + ) + _metrics_spec = { + measure.name: { + "fieldName": measure.field_name, + "name": measure.name, + "type": DRUID_AGG_MAPPING[(measure.type.lower(), measure.agg.lower())], + } + for measure_group in cube_config.measures.values() # type: ignore + for measure in measure_group.measures + } + + metrics_spec = list(_metrics_spec.values()) + temporal_partitions = ( + [ + partition + for partition in cube_config.partitions + if partition.type_ == PartitionType.TEMPORAL + ] + if cube_config.partitions + else [] + ) + if not temporal_partitions: + raise DJException( + "Druid ingestion requires a temporal partition to be specified", + ) + + druid_spec = { + "dataSchema": { + "dataSource": druid_datasource_name, + "parser": { + "parseSpec": { + "format": cube_config.druid.parse_spec_format or "parquet", # type: ignore + "dimensionsSpec": {"dimensions": cube_config.dimensions}, + "timestampSpec": { + "column": ( + cube_config.druid.timestamp_column # type: ignore + or temporal_partitions[0].name # type: ignore + ), + "format": ( + cube_config.druid.timestamp_format # type: ignore + or "yyyyMMdd" + ), + }, + }, + }, + "metricsSpec": metrics_spec, + "granularitySpec": { + "type": "uniform", + "segmentGranularity": cube_config.druid.granularity, # type: ignore + "intervals": ( + cube_config.druid.intervals or temporal_partitions[0].range # type: ignore + ), + }, + }, + } + return druid_spec + + def schedule( + self, + materialization: Materialization, + query_service_client: QueryServiceClient, + ) -> MaterializationInfo: + """ + Use the query service to kick off the materialization setup. + """ + cube_config = DruidCubeConfig.parse_obj(materialization.config) + druid_spec = self.build_druid_spec( + cube_config, + materialization.node_revision.name, + ) + return query_service_client.materialize( + DruidMaterializationInput( + name=materialization.name, + node_name=materialization.node_revision.name, + node_version=materialization.node_revision.version, + node_type=materialization.node_revision.type, + schedule=materialization.schedule, + query=cube_config.query, + spark_conf=cube_config.spark.__root__, + druid_spec=druid_spec, + partitions=cube_config.partitions, + upstream_tables=cube_config.upstream_tables or [], + ), + ) diff --git a/datajunction-server/datajunction_server/materialization/jobs/materialization_job.py b/datajunction-server/datajunction_server/materialization/jobs/materialization_job.py new file mode 100644 index 000000000..9d947aabf --- /dev/null +++ b/datajunction-server/datajunction_server/materialization/jobs/materialization_job.py @@ -0,0 +1,91 @@ +""" +Available materialization jobs. +""" +import abc +from typing import Optional + +from datajunction_server.models.engine import Dialect +from datajunction_server.models.materialization import ( + GenericMaterializationConfig, + GenericMaterializationInput, + Materialization, + MaterializationInfo, +) +from datajunction_server.service_clients import QueryServiceClient + + +class MaterializationJob(abc.ABC): # pylint: disable=too-few-public-methods + """ + Base class for a materialization job + """ + + dialect: Optional[Dialect] = None + + def __init__(self): + ... + + @abc.abstractmethod + def schedule( + self, + materialization: Materialization, + query_service_client: QueryServiceClient, + ) -> MaterializationInfo: + """ + Schedules the materialization job, typically done by calling a separate service + with the configured materialization parameters. + """ + + +class TrinoMaterializationJob( # pylint: disable=too-few-public-methods # pragma: no cover + MaterializationJob, +): + """ + Trino materialization job. Left unimplemented for the time being. + """ + + dialect = Dialect.TRINO + + def schedule( + self, + materialization: Materialization, + query_service_client: QueryServiceClient, + ) -> MaterializationInfo: + """ + Placeholder for the actual implementation. + """ + + +class SparkSqlMaterializationJob( # pylint: disable=too-few-public-methods # pragma: no cover + MaterializationJob, +): + """ + Spark SQL materialization job. Left unimplemented for the time being. + """ + + dialect = Dialect.SPARK + + def schedule( + self, + materialization: Materialization, + query_service_client: QueryServiceClient, + ) -> MaterializationInfo: + """ + Placeholder for the actual implementation. + """ + generic_config = GenericMaterializationConfig.parse_obj(materialization.config) + result = query_service_client.materialize( + GenericMaterializationInput( + name=materialization.name, # type: ignore + node_name=materialization.node_revision.name, + node_version=materialization.node_revision.version, + node_type=materialization.node_revision.type.value, + schedule=materialization.schedule, + query=generic_config.query, + upstream_tables=generic_config.upstream_tables, + spark_conf=generic_config.spark.__root__, + partitions=[ + partition.dict() for partition in generic_config.partitions + ], + ), + ) + return result diff --git a/datajunction-server/datajunction_server/models/__init__.py b/datajunction-server/datajunction_server/models/__init__.py new file mode 100644 index 000000000..2daf1eeb2 --- /dev/null +++ b/datajunction-server/datajunction_server/models/__init__.py @@ -0,0 +1,29 @@ +""" +All models. +""" + +__all__ = [ + "AttributeType", + "ColumnAttribute", + "Catalog", + "Column", + "Database", + "Engine", + "History", + "Node", + "NodeRevision", + "Table", + "Tag", + "User", +] + +from datajunction_server.models.attribute import AttributeType, ColumnAttribute +from datajunction_server.models.catalog import Catalog +from datajunction_server.models.column import Column +from datajunction_server.models.database import Database +from datajunction_server.models.engine import Engine +from datajunction_server.models.history import History +from datajunction_server.models.node import Node, NodeRevision +from datajunction_server.models.table import Table +from datajunction_server.models.tag import Tag +from datajunction_server.models.user import User diff --git a/datajunction-server/datajunction_server/models/attribute.py b/datajunction-server/datajunction_server/models/attribute.py new file mode 100644 index 000000000..e3a720c60 --- /dev/null +++ b/datajunction-server/datajunction_server/models/attribute.py @@ -0,0 +1,84 @@ +""" +Models for attributes. +""" +import enum +from typing import TYPE_CHECKING, List, Optional + +from sqlalchemy import JSON, String, UniqueConstraint +from sqlalchemy.sql.schema import Column as SqlaColumn +from sqlmodel import Field, Relationship + +from datajunction_server.models.base import BaseSQLModel +from datajunction_server.models.node import NodeType + +if TYPE_CHECKING: + from datajunction_server.models import Column + + +RESERVED_ATTRIBUTE_NAMESPACE = "system" + + +class MutableAttributeTypeFields(BaseSQLModel): + """ + Fields on attribute types that users can set. + """ + + namespace: str + name: str = Field(sa_column=SqlaColumn("name", String)) + description: str + allowed_node_types: List[NodeType] = Field(sa_column=SqlaColumn(JSON)) + + +class UniquenessScope(str, enum.Enum): + """ + The scope at which this attribute needs to be unique. + """ + + NODE = "node" + COLUMN_TYPE = "column_type" + + +class RestrictedAttributeTypeFields(BaseSQLModel): + """ + Fields on attribute types that aren't configurable by users. + """ + + uniqueness_scope: List[UniquenessScope] = Field( + default=[], + sa_column=SqlaColumn(JSON), + ) + + +class AttributeTypeBase(MutableAttributeTypeFields, RestrictedAttributeTypeFields): + """Base attribute type.""" + + +class AttributeType(AttributeTypeBase, table=True): # type: ignore # pylint: disable=too-many-ancestors + """ + Available attribute types for column metadata. + """ + + __table_args__ = (UniqueConstraint("namespace", "name"),) + + id: Optional[int] = Field(default=None, primary_key=True) + + def __hash__(self): + return hash(self.id) + + +class ColumnAttribute(BaseSQLModel, table=True): # type: ignore + """ + Column attributes. + """ + + __table_args__ = (UniqueConstraint("attribute_type_id", "column_id"),) + + id: Optional[int] = Field(default=None, primary_key=True) + attribute_type_id: Optional[int] = Field( + default=None, + foreign_key="attributetype.id", + ) + attribute_type: AttributeType = Relationship() + + column_id: Optional[int] = Field(default=None, foreign_key="column.id") + column: "Column" = Relationship(back_populates="attributes") diff --git a/datajunction-server/datajunction_server/models/base.py b/datajunction-server/datajunction_server/models/base.py new file mode 100644 index 000000000..f281a689c --- /dev/null +++ b/datajunction-server/datajunction_server/models/base.py @@ -0,0 +1,72 @@ +""" +A base SQLModel class with a default naming convention. +""" +from typing import Optional + +from sqlalchemy.engine.default import DefaultExecutionContext +from sqlmodel import Field, SQLModel + +NAMING_CONVENTION = { + "ix": "ix_%(column_0_label)s", + "uq": "uq_%(table_name)s_%(column_0_name)s", + "ck": "ck_%(table_name)s_%(auto_constraint_name)s", + "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s", + "pk": "pk_%(table_name)s", +} + + +class BaseSQLModel(SQLModel): + """ + Base model object with naming convention for constraints. This forces alembic's + autogenerate functionality to generate constraints with explicit names. + """ + + metadata = SQLModel.metadata + metadata.naming_convention = NAMING_CONVENTION + + def update(self, data: dict) -> "BaseSQLModel": + """ + Helper method that updates the current model with new data and validates. + """ + update = self.dict() + update.update(data) + for key, value in self.validate(update).dict(exclude_defaults=True).items(): + setattr(self, key, value) + return self + + +def labelize(value: str) -> str: + """ + Turn a system name into a human-readable name. + """ + + return value.replace(".", ": ").replace("_", " ").title() + + +def generate_display_name(column_name: str): + """ + SQLAlchemy helper to generate a human-readable version of the given system name. + """ + + def default_function(context: DefaultExecutionContext) -> str: + column_value = context.current_parameters.get(column_name) + return labelize(column_value) + + return default_function + + +class NodeColumns(BaseSQLModel, table=True): # type: ignore + """ + Join table for node columns. + """ + + node_id: Optional[int] = Field( + default=None, + foreign_key="noderevision.id", + primary_key=True, + ) + column_id: Optional[int] = Field( + default=None, + foreign_key="column.id", + primary_key=True, + ) diff --git a/datajunction-server/datajunction_server/models/catalog.py b/datajunction-server/datajunction_server/models/catalog.py new file mode 100644 index 000000000..2ab0df77f --- /dev/null +++ b/datajunction-server/datajunction_server/models/catalog.py @@ -0,0 +1,78 @@ +""" +Models for columns. +""" +from datetime import datetime, timezone +from functools import partial +from typing import TYPE_CHECKING, Dict, List, Optional +from uuid import UUID, uuid4 + +from sqlalchemy import DateTime +from sqlalchemy.sql.schema import Column as SqlaColumn +from sqlalchemy_utils import UUIDType +from sqlmodel import JSON, Field, Relationship, SQLModel + +from datajunction_server.models.base import BaseSQLModel +from datajunction_server.models.engine import Engine, EngineInfo +from datajunction_server.typing import UTCDatetime + +if TYPE_CHECKING: + from datajunction_server.models import NodeRevision, Table + + +class CatalogEngines(BaseSQLModel, table=True): # type: ignore + """ + Join table for catalogs and engines. + """ + + catalog_id: Optional[int] = Field( + default=None, + foreign_key="catalog.id", + primary_key=True, + ) + engine_id: Optional[int] = Field( + default=None, + foreign_key="engine.id", + primary_key=True, + ) + + +class Catalog(BaseSQLModel, table=True): # type: ignore + """ + A catalog. + """ + + id: Optional[int] = Field(default=None, primary_key=True) + uuid: UUID = Field(default_factory=uuid4, sa_column=SqlaColumn(UUIDType())) + name: str + engines: List[Engine] = Relationship( + link_model=CatalogEngines, + sa_relationship_kwargs={ + "primaryjoin": "Catalog.id==CatalogEngines.catalog_id", + "secondaryjoin": "Engine.id==CatalogEngines.engine_id", + }, + ) + node_revisions: List["NodeRevision"] = Relationship(back_populates="catalog") + created_at: UTCDatetime = Field( + sa_column=SqlaColumn(DateTime(timezone=True)), + default_factory=partial(datetime.now, timezone.utc), + ) + updated_at: UTCDatetime = Field( + sa_column=SqlaColumn(DateTime(timezone=True)), + default_factory=partial(datetime.now, timezone.utc), + ) + extra_params: Dict = Field(default={}, sa_column=SqlaColumn(JSON)) + + def __str__(self) -> str: + return self.name + + def __hash__(self) -> int: + return hash(self.id) + + +class CatalogInfo(SQLModel): + """ + Class for catalog creation + """ + + name: str + engines: List[EngineInfo] = [] diff --git a/datajunction-server/datajunction_server/models/column.py b/datajunction-server/datajunction_server/models/column.py new file mode 100644 index 000000000..2aa970b9e --- /dev/null +++ b/datajunction-server/datajunction_server/models/column.py @@ -0,0 +1,131 @@ +""" +Models for columns. +""" +from typing import TYPE_CHECKING, List, Optional, Tuple, TypedDict + +from pydantic import root_validator +from sqlalchemy import TypeDecorator +from sqlalchemy.sql.schema import Column as SqlaColumn +from sqlalchemy.types import Text +from sqlmodel import Field, Relationship + +from datajunction_server.models.base import BaseSQLModel, NodeColumns +from datajunction_server.sql.parsing.types import ColumnType + +if TYPE_CHECKING: + from datajunction_server.models.attribute import ColumnAttribute + from datajunction_server.models.node import Node, NodeRevision + + +class ColumnYAML(TypedDict, total=False): + """ + Schema of a column in the YAML file. + """ + + type: str + dimension: str + + +class ColumnTypeDecorator(TypeDecorator): # pylint: disable=abstract-method + """ + Converts a column type from the database to a `ColumnType` class + """ + + impl = Text + + def process_bind_param(self, value: ColumnType, dialect): + return str(value) + + def process_result_value(self, value, dialect): + from datajunction_server.sql.parsing.backends.antlr4 import ( # pylint: disable=import-outside-toplevel + parse_rule, + ) + + if not value: + return value + return parse_rule(value, "dataType") + + +class Column(BaseSQLModel, table=True): # type: ignore + """ + A column. + + Columns can be physical (associated with ``Table`` objects) or abstract (associated + with ``Node`` objects). + """ + + id: Optional[int] = Field(default=None, primary_key=True) + name: str + type: ColumnType = Field(sa_column=SqlaColumn(ColumnTypeDecorator, nullable=False)) + + dimension_id: Optional[int] = Field(default=None, foreign_key="node.id") + dimension: "Node" = Relationship( + sa_relationship_kwargs={ + "lazy": "joined", + }, + ) + dimension_column: Optional[str] = None + node_revisions: List["NodeRevision"] = Relationship( + back_populates="columns", + link_model=NodeColumns, + sa_relationship_kwargs={ + "lazy": "select", + }, + ) + attributes: List["ColumnAttribute"] = Relationship( + back_populates="column", + sa_relationship_kwargs={ + "lazy": "joined", + }, + ) + + def identifier(self) -> Tuple[str, ColumnType]: + """ + Unique identifier for this column. + """ + return self.name, self.type + + def has_dimension_attribute(self) -> bool: + """ + Whether the dimension attribute is set on this column. + """ + return self.has_attribute("dimension") + + def has_primary_key_attribute(self) -> bool: + """ + Whether the primary key attribute is set on this column. + """ + return self.has_attribute("primary_key") + + def has_attribute(self, attribute_name: str) -> bool: + """ + Whether the given attribute is set on this column. + """ + return any( + attr.attribute_type.name == attribute_name + for attr in self.attributes # pylint: disable=not-an-iterable + ) + + def __hash__(self) -> int: + return hash(self.id) + + class Config: # pylint: disable=missing-class-docstring, too-few-public-methods + arbitrary_types_allowed = True + + @root_validator + def type_string(cls, values): # pylint: disable=no-self-argument + """ + Processes the column type + """ + values["type"] = str(values.get("type")) + return values + + +class ColumnAttributeInput(BaseSQLModel): + """ + A column attribute input + """ + + attribute_type_namespace: Optional[str] = "system" + attribute_type_name: str + column_name: str diff --git a/datajunction-server/datajunction_server/models/cube.py b/datajunction-server/datajunction_server/models/cube.py new file mode 100644 index 000000000..d10e06d0e --- /dev/null +++ b/datajunction-server/datajunction_server/models/cube.py @@ -0,0 +1,55 @@ +""" +Models for cubes. +""" + +from typing import List, Optional + +from pydantic import Field, root_validator +from sqlmodel import SQLModel + +from datajunction_server.models.materialization import MaterializationConfigOutput +from datajunction_server.models.node import AvailabilityState, ColumnOutput, NodeType +from datajunction_server.typing import UTCDatetime + + +class CubeElementMetadata(SQLModel): + """ + Metadata for an element in a cube + """ + + name: str + node_name: str + type: str + + @root_validator(pre=True) + def type_string(cls, values): # pylint: disable=no-self-argument + """ + Extracts the type as a string + """ + values = dict(values) + values["node_name"] = values["node_revisions"][0].name + values["type"] = values["node_revisions"][0].type + return values + + +class CubeRevisionMetadata(SQLModel): + """ + Metadata for a cube node + """ + + id: int = Field(alias="node_revision_id") + node_id: int + type: NodeType + name: str + display_name: str + version: str + description: str = "" + availability: Optional[AvailabilityState] = None + cube_elements: List[CubeElementMetadata] + query: str + columns: List[ColumnOutput] + updated_at: UTCDatetime + materializations: List[MaterializationConfigOutput] + + class Config: # pylint: disable=missing-class-docstring,too-few-public-methods + allow_population_by_field_name = True diff --git a/datajunction-server/datajunction_server/models/database.py b/datajunction-server/datajunction_server/models/database.py new file mode 100644 index 000000000..f76badb98 --- /dev/null +++ b/datajunction-server/datajunction_server/models/database.py @@ -0,0 +1,72 @@ +""" +Models for databases. +""" + +from datetime import datetime, timezone +from functools import partial +from typing import TYPE_CHECKING, Dict, List, Optional, TypedDict +from uuid import UUID, uuid4 + +from sqlalchemy import DateTime, String +from sqlalchemy.sql.schema import Column as SqlaColumn +from sqlalchemy_utils import UUIDType +from sqlmodel import JSON, Field, Relationship + +from datajunction_server.models.base import BaseSQLModel +from datajunction_server.typing import UTCDatetime + +if TYPE_CHECKING: + from datajunction_server.models.catalog import Catalog + from datajunction_server.models.table import Table + + +# Schema of a database in the YAML file. +DatabaseYAML = TypedDict( + "DatabaseYAML", + {"description": str, "URI": str, "read-only": bool, "async_": bool, "cost": float}, + total=False, +) + + +class Database(BaseSQLModel, table=True): # type: ignore + """ + A database. + + A simple example: + + name: druid + description: An Apache Druid database + URI: druid://localhost:8082/druid/v2/sql/ + read-only: true + async_: false + cost: 1.0 + + """ + + id: Optional[int] = Field(default=None, primary_key=True) + uuid: UUID = Field(default_factory=uuid4, sa_column=SqlaColumn(UUIDType())) + + name: str = Field(sa_column=SqlaColumn("name", String, unique=True)) + description: str = "" + URI: str + extra_params: Dict = Field(default={}, sa_column=SqlaColumn(JSON)) + read_only: bool = True + async_: bool = Field(default=False, sa_column_kwargs={"name": "async"}) + cost: float = 1.0 + + created_at: UTCDatetime = Field( + sa_column=SqlaColumn(DateTime(timezone=True)), + default_factory=partial(datetime.now, timezone.utc), + ) + updated_at: UTCDatetime = Field( + sa_column=SqlaColumn(DateTime(timezone=True)), + default_factory=partial(datetime.now, timezone.utc), + ) + + tables: List["Table"] = Relationship( + back_populates="database", + sa_relationship_kwargs={"cascade": "all, delete"}, + ) + + def __hash__(self) -> int: + return hash(self.id) diff --git a/datajunction-server/datajunction_server/models/engine.py b/datajunction-server/datajunction_server/models/engine.py new file mode 100644 index 000000000..a21ab4eec --- /dev/null +++ b/datajunction-server/datajunction_server/models/engine.py @@ -0,0 +1,53 @@ +""" +Models for columns. +""" +import enum +from typing import Optional + +from sqlalchemy.sql.schema import Column as SqlaColumn +from sqlalchemy.types import Enum +from sqlmodel import Field, SQLModel + +from datajunction_server.models.base import BaseSQLModel + + +class Dialect(str, enum.Enum): + """ + SQL dialect + """ + + SPARK = "spark" + TRINO = "trino" + DRUID = "druid" + + +class Engine(BaseSQLModel, table=True): # type: ignore + """ + A query engine. + """ + + id: Optional[int] = Field(default=None, primary_key=True) + name: str + version: str + uri: Optional[str] + dialect: Optional[Dialect] = Field(sa_column=SqlaColumn(Enum(Dialect))) + + +class EngineInfo(SQLModel): + """ + Class for engine creation + """ + + name: str + version: str + uri: Optional[str] + dialect: Optional[Dialect] + + +class EngineRef(SQLModel): + """ + Basic reference to an engine + """ + + name: str + version: str diff --git a/datajunction-server/datajunction_server/models/history.py b/datajunction-server/datajunction_server/models/history.py new file mode 100644 index 000000000..8f5f0124d --- /dev/null +++ b/datajunction-server/datajunction_server/models/history.py @@ -0,0 +1,91 @@ +""" +Model for history. +""" +from datetime import datetime, timezone +from enum import Enum +from functools import partial +from typing import Any, Dict, Optional + +from sqlalchemy import DateTime +from sqlalchemy.sql.schema import Column as SqlaColumn +from sqlmodel import JSON, Field, SQLModel + +from datajunction_server.models.node import NodeRevision, NodeStatus +from datajunction_server.typing import UTCDatetime + + +class ActivityType(str, Enum): + """ + An activity type + """ + + CREATE = "create" + DELETE = "delete" + RESTORE = "restore" + UPDATE = "update" + REFRESH = "refresh" + TAG = "tag" + SET_ATTRIBUTE = "set_attribute" + STATUS_CHANGE = "status_change" + + +class EntityType(str, Enum): + """ + An entity type for which activity can occur + """ + + ATTRIBUTE = "attribute" + AVAILABILITY = "availability" + CATALOG = "catalog" + COLUMN_ATTRIBUTE = "column_attribute" + DEPENDENCY = "dependency" + ENGINE = "engine" + LINK = "link" + MATERIALIZATION = "materialization" + NAMESPACE = "namespace" + NODE = "node" + QUERY = "query" + TAG = "tag" + + +class History(SQLModel, table=True): # type: ignore + """ + An event to store as part of the server's activity history + """ + + id: Optional[int] = Field(default=None, primary_key=True) + entity_type: Optional[EntityType] = Field(default=None) + entity_name: Optional[str] = Field(default=None) + node: Optional[str] = Field(default=None) + activity_type: Optional[ActivityType] = Field(default=None) + user: Optional[str] = Field(default=None) + pre: Dict[str, Any] = Field(default_factory=dict, sa_column=SqlaColumn(JSON)) + post: Dict[str, Any] = Field(default_factory=dict, sa_column=SqlaColumn(JSON)) + details: Dict[str, Any] = Field(default_factory=dict, sa_column=SqlaColumn(JSON)) + created_at: UTCDatetime = Field( + sa_column=SqlaColumn(DateTime(timezone=True)), + default_factory=partial(datetime.now, timezone.utc), + ) + + def __hash__(self) -> int: + return hash(self.id) + + +def status_change_history( + node_revision: NodeRevision, + start_status: NodeStatus, + end_status: NodeStatus, + parent_node: str = None, +) -> History: + """ + Returns a status change history activity entry + """ + return History( + entity_type=EntityType.NODE, + entity_name=node_revision.name, + node=node_revision.name, + activity_type=ActivityType.STATUS_CHANGE, + pre={"status": start_status}, + post={"status": end_status}, + details={"upstream_node": parent_node if parent_node else None}, + ) diff --git a/datajunction-server/datajunction_server/models/materialization.py b/datajunction-server/datajunction_server/models/materialization.py new file mode 100644 index 000000000..a98e5e8e8 --- /dev/null +++ b/datajunction-server/datajunction_server/models/materialization.py @@ -0,0 +1,295 @@ +"""Models for materialization""" +import enum +import zlib +from typing import TYPE_CHECKING, Dict, List, Optional, Union + +from pydantic import AnyHttpUrl, BaseModel, validator +from sqlalchemy import JSON +from sqlalchemy import Column as SqlaColumn +from sqlalchemy import DateTime, String +from sqlmodel import Field, Relationship, SQLModel + +from datajunction_server.models.base import BaseSQLModel +from datajunction_server.models.engine import Engine, EngineInfo, EngineRef +from datajunction_server.typing import UTCDatetime + +if TYPE_CHECKING: + from datajunction_server.models import NodeRevision + + +class GenericMaterializationInput(BaseModel): + """ + The input when calling the query service's materialization + API endpoint for a generic node. + """ + + name: str + node_name: str + node_version: str + node_type: str + schedule: str + query: str + upstream_tables: List[str] + spark_conf: Optional[Dict] = None + partitions: Optional[List[Dict]] = None + + +class DruidMaterializationInput(GenericMaterializationInput): + """ + The input when calling the query service's materialization + API endpoint for a cube node. + """ + + druid_spec: Dict + + +class MaterializationInfo(BaseModel): + """ + The output when calling the query service's materialization + API endpoint for a cube node. + """ + + output_tables: List[str] + urls: List[AnyHttpUrl] + + +class MaterializationConfigOutput(SQLModel): + """ + Output for materialization config. + """ + + name: Optional[str] + engine: EngineInfo + config: Dict + schedule: str + job: str + + +class MaterializationConfigInfoUnified( + MaterializationInfo, + MaterializationConfigOutput, +): + """ + Materialization config + info + """ + + +class SparkConf(BaseSQLModel): + """Spark configuration""" + + __root__: Dict[str, str] + + +class PartitionType(str, enum.Enum): + """ + Partition type. + + A partition can be temporal or categorical + """ + + TEMPORAL = "temporal" + CATEGORICAL = "categorical" + + +class Partition(BaseSQLModel): + """ + A partition specification tells the ongoing and backfill materialization jobs how to partition + the materialized dataset and which partition values (a list or range of values) to operate on. + Partitions may be temporal or categorical and will be handled differently depending on the type. + + For temporal partition types, the ongoing materialization job will continue to operate on the + latest partitions and the partition values specified by `values` and `range` are only relevant + to the backfill job. + + Examples: + This will tell DJ to backfill for all values of the dateint partition: + Partition(name=“dateint”, type="temporal", values=[], range=()) + This will tell DJ to backfill just 20230601 and 20230605: + Partition(name=“dateint”, type="temporal", values=[20230601, 20230605], range=()) + This will tell DJ to backfill 20230601 and between 20220101 and 20230101: + Partition(name=“dateint”, type="temporal", values=[20230601], range=(20220101, 20230101)) + + For categorical partition types, the ongoing materialization job will *only* operate on the + specified partition values in `values` and `range`: + Partition(name=“group_id”, type="categorical", values=["a", "b", "c"], range=()) + """ + + name: str + values: Optional[List] + range: Optional[List] + + # This expression evaluates to the temporal partition value for scheduled runs + expression: Optional[str] + + type_: PartitionType + + +class GenericMaterializationConfigInput(BaseModel): + """ + User-input portions of the materialization config + """ + + # List of partitions that materialization jobs (ongoing and backfill) will operate on. + partitions: Optional[List[Partition]] + # Spark config + spark: Optional[SparkConf] + + +class GenericMaterializationConfig(GenericMaterializationConfigInput): + """ + Generic node materialization config needed by any materialization choices + and engine combinations + """ + + query: Optional[str] + upstream_tables: Optional[List[str]] + + def identifier(self) -> str: + """ + Generates an identifier for this materialization config that is used by default + for the materialization config's name if one is not set. Note that this name is + based on partition names (both temporal and categorical) and partition values + (only categorical). + """ + entities = ["default"] if not self.partitions else [] + partitions_values = "" + if self.partitions: + for partition in self.partitions: + if partition.type_ != PartitionType.TEMPORAL: + if partition.values: + partitions_values += str(partition.values) + if partition.range is not None: # pragma: no cover + partitions_values += str(partition.range) + entities.append(partition.name) + entities.append(str(zlib.crc32(partitions_values.encode("utf-8")))) + return "_".join(entities) + + +class DruidConf(BaseSQLModel): + """Druid configuration""" + + granularity: str + intervals: Optional[List[str]] + timestamp_column: str + timestamp_format: Optional[str] + parse_spec_format: Optional[str] + + +class Measure(SQLModel): + """ + A measure with a simple aggregation + """ + + name: str + field_name: str + agg: str + type: str + + def __eq__(self, other): + return tuple(self.__dict__.items()) == tuple( + other.__dict__.items(), + ) # pragma: no cover + + def __hash__(self): + return hash(tuple(self.__dict__.items())) # pragma: no cover + + +class MetricMeasures(SQLModel): + """ + Represent a metric as a set of measures, along with the expression for + combining the measures to make the metric. + """ + + metric: str + measures: List[Measure] # + combiner: str + + +class GenericCubeConfigInput(GenericMaterializationConfigInput): + """ + Generic cube materialization config fields that require user input + """ + + dimensions: Optional[List[str]] + measures: Optional[Dict[str, MetricMeasures]] + + +class GenericCubeConfig(GenericCubeConfigInput, GenericMaterializationConfig): + """ + Generic cube materialization config needed by any materialization + choices and engine combinations + """ + + +class DruidCubeConfigInput(GenericCubeConfigInput): + """ + Specific Druid cube materialization fields that require user input + """ + + prefix: Optional[str] = "" + suffix: Optional[str] = "" + druid: DruidConf + + +class DruidCubeConfig(DruidCubeConfigInput, GenericCubeConfig): + """ + Specific cube materialization implementation with Spark and Druid ingestion and + optional prefix and/or suffix to include with the materialized entity's name. + """ + + +class Materialization(BaseSQLModel, table=True): # type: ignore + """ + Materialization configured for a node. + """ + + node_revision_id: int = Field(foreign_key="noderevision.id", primary_key=True) + node_revision: "NodeRevision" = Relationship( + back_populates="materializations", + ) + + engine_id: int = Field(foreign_key="engine.id", primary_key=True) + engine: Engine = Relationship() + + name: Optional[str] = Field(primary_key=True) + + # A cron schedule to materialize this node by + schedule: str + + # Arbitrary config relevant to the materialization engine + config: Union[GenericMaterializationConfig, DruidCubeConfig] = Field( + default={}, + sa_column=SqlaColumn(JSON), + ) + + # The name of the plugin that handles materialization, if any + job: str = Field( + default="MaterializationJob", + sa_column=SqlaColumn("job", String), + ) + + deactivated_at: UTCDatetime = Field( + nullable=True, + sa_column=SqlaColumn(DateTime(timezone=True)), + default=None, + ) + + @validator("config") + def config_validator(cls, value): # pylint: disable=no-self-argument + """Changes `config` to a dict prior to saving""" + return value.dict() + + +class UpsertMaterialization(BaseSQLModel): + """ + An upsert object for materialization configs + """ + + name: Optional[str] + engine: EngineRef + config: Union[ + DruidCubeConfigInput, + GenericCubeConfigInput, + GenericMaterializationConfigInput, + ] + schedule: str diff --git a/datajunction-server/datajunction_server/models/metric.py b/datajunction-server/datajunction_server/models/metric.py new file mode 100644 index 000000000..20ba7809f --- /dev/null +++ b/datajunction-server/datajunction_server/models/metric.py @@ -0,0 +1,57 @@ +""" +Models for metrics. +""" +from typing import List, Optional + +from sqlmodel import SQLModel + +from datajunction_server.models.engine import Dialect +from datajunction_server.models.node import DimensionAttributeOutput, Node +from datajunction_server.models.query import ColumnMetadata +from datajunction_server.sql.dag import get_dimensions +from datajunction_server.typing import UTCDatetime + + +class Metric(SQLModel): + """ + Class for a metric. + """ + + id: int + name: str + display_name: str + current_version: str + description: str = "" + + created_at: UTCDatetime + updated_at: UTCDatetime + + query: str + + dimensions: List[DimensionAttributeOutput] + + @classmethod + def parse_node(cls, node: Node) -> "Metric": + """ + Parses a node into a metric. + """ + + return cls( + **node.dict(), + description=node.current.description, + updated_at=node.current.updated_at, + query=node.current.query, + dimensions=get_dimensions(node), + ) + + +class TranslatedSQL(SQLModel): + """ + Class for SQL generated from a given metric. + """ + + # TODO: once type-inference is added to /query/ endpoint # pylint: disable=fixme + # columns attribute can be required + sql: str + columns: Optional[List[ColumnMetadata]] = None # pragma: no-cover + dialect: Optional[Dialect] = None diff --git a/datajunction-server/datajunction_server/models/node.py b/datajunction-server/datajunction_server/models/node.py new file mode 100644 index 000000000..77b608c36 --- /dev/null +++ b/datajunction-server/datajunction_server/models/node.py @@ -0,0 +1,1139 @@ +# pylint: disable=too-many-instance-attributes,too-many-lines,too-many-ancestors +""" +Model for nodes. +""" +import enum +import sys +from dataclasses import dataclass +from datetime import datetime, timezone +from functools import partial +from http import HTTPStatus +from typing import Dict, List, Optional, Tuple + +from pydantic import BaseModel, Extra +from pydantic import Field as PydanticField +from pydantic import root_validator, validator +from sqlalchemy import JSON, DateTime, String +from sqlalchemy.sql.schema import Column as SqlaColumn +from sqlalchemy.sql.schema import UniqueConstraint +from sqlalchemy.types import Enum +from sqlmodel import Field, Relationship, SQLModel +from typing_extensions import TypedDict + +from datajunction_server.errors import DJError, DJInvalidInputException +from datajunction_server.models.base import ( + BaseSQLModel, + NodeColumns, + generate_display_name, +) +from datajunction_server.models.catalog import Catalog +from datajunction_server.models.column import Column, ColumnYAML +from datajunction_server.models.database import Database +from datajunction_server.models.engine import Dialect +from datajunction_server.models.materialization import ( + Materialization, + MaterializationConfigOutput, +) +from datajunction_server.models.tag import Tag, TagNodeRelationship +from datajunction_server.sql.parsing.types import ColumnType +from datajunction_server.typing import UTCDatetime +from datajunction_server.utils import Version, amenable_name + +DEFAULT_DRAFT_VERSION = Version(major=0, minor=1) +DEFAULT_PUBLISHED_VERSION = Version(major=1, minor=0) + + +@dataclass(frozen=True) +class BuildCriteria: + """ + Criterion used for building + - used to deterimine whether to use an availability state + """ + + timestamp: Optional[UTCDatetime] = None + dialect: Dialect = Dialect.SPARK + for_materialization: bool = False + + +class NodeRelationship(BaseSQLModel, table=True): # type: ignore + """ + Join table for self-referential many-to-many relationships between nodes. + """ + + parent_id: Optional[int] = Field( + default=None, + foreign_key="node.id", + primary_key=True, + ) + + # This will default to `latest`, which points to the current version of the node, + # or it can be a specific version. + parent_version: Optional[str] = Field( + default="latest", + ) + + child_id: Optional[int] = Field( + default=None, + foreign_key="noderevision.id", + primary_key=True, + ) + + +class CubeRelationship(BaseSQLModel, table=True): # type: ignore + """ + Join table for many-to-many relationships between cube nodes and metric/dimension nodes. + """ + + __tablename__ = "cube" + + cube_id: Optional[int] = Field( + default=None, + foreign_key="noderevision.id", + primary_key=True, + ) + + cube_element_id: Optional[int] = Field( + default=None, + foreign_key="column.id", + primary_key=True, + ) + + +class BoundDimensionsRelationship(BaseSQLModel, table=True): # type: ignore + """ + Join table for many-to-many relationships between metric nodes + and parent nodes for dimensions that are required. + """ + + __tablename__ = "metric_required_dimensions" + + metric_id: Optional[int] = Field( + default=None, + foreign_key="noderevision.id", + primary_key=True, + ) + + bound_dimension_id: Optional[int] = Field( + default=None, + foreign_key="column.id", + primary_key=True, + ) + + +class NodeType(str, enum.Enum): + """ + Node type. + + A node can have 4 types, currently: + + 1. SOURCE nodes are root nodes in the DAG, and point to tables or views in a DB. + 2. TRANSFORM nodes are SQL transformations, reading from SOURCE/TRANSFORM nodes. + 3. METRIC nodes are leaves in the DAG, and have a single aggregation query. + 4. DIMENSION nodes are special SOURCE nodes that can be auto-joined with METRICS. + 5. CUBE nodes contain a reference to a set of METRICS and a set of DIMENSIONS. + """ + + SOURCE = "source" + TRANSFORM = "transform" + METRIC = "metric" + DIMENSION = "dimension" + CUBE = "cube" + + +class NodeMode(str, enum.Enum): + """ + Node mode. + + A node can be in one of the following modes: + + 1. PUBLISHED - Must be valid and not cause any child nodes to be invalid + 2. DRAFT - Can be invalid, have invalid parents, and include dangling references + """ + + PUBLISHED = "published" + DRAFT = "draft" + + +class NodeStatus(str, enum.Enum): + """ + Node status. + + A node can have one of the following statuses: + + 1. VALID - All references to other nodes and node columns are valid + 2. INVALID - One or more parent nodes are incompatible or do not exist + """ + + VALID = "valid" + INVALID = "invalid" + + +class NodeYAML(TypedDict, total=False): + """ + Schema of a node in the YAML file. + """ + + description: str + display_name: str + type: NodeType + query: str + columns: Dict[str, ColumnYAML] + + +class NodeBase(BaseSQLModel): + """ + A base node. + """ + + name: str = Field(sa_column=SqlaColumn("name", String, unique=True)) + type: NodeType = Field(sa_column=SqlaColumn(Enum(NodeType))) + display_name: Optional[str] = Field( + sa_column=SqlaColumn( + "display_name", + String, + default=generate_display_name("name"), + ), + max_length=100, + ) + + +class NodeRevisionBase(BaseSQLModel): + """ + A base node revision. + """ + + name: str = Field( + sa_column=SqlaColumn("name", String, unique=False), + foreign_key="node.name", + ) + display_name: Optional[str] = Field( + sa_column=SqlaColumn( + "display_name", + String, + default=generate_display_name("name"), + ), + ) + type: NodeType = Field(sa_column=SqlaColumn(Enum(NodeType))) + description: str = "" + query: Optional[str] = None + mode: NodeMode = NodeMode.PUBLISHED + + +class MissingParent(BaseSQLModel, table=True): # type: ignore + """ + A missing parent node + """ + + id: Optional[int] = Field(default=None, primary_key=True) + name: str = Field(sa_column=SqlaColumn("name", String)) + created_at: UTCDatetime = Field( + sa_column=SqlaColumn(DateTime(timezone=True)), + default_factory=partial(datetime.now, timezone.utc), + ) + + +class NodeMissingParents(BaseSQLModel, table=True): # type: ignore + """ + Join table for missing parents + """ + + missing_parent_id: Optional[int] = Field( + default=None, + foreign_key="missingparent.id", + primary_key=True, + ) + referencing_node_id: Optional[int] = Field( + default=None, + foreign_key="noderevision.id", + primary_key=True, + ) + + +class TemporalPartitionRange(BaseSQLModel): + """ + Any temporal partition range with a min and max partition. + """ + + min_temporal_partition: Optional[List[str]] = None + max_temporal_partition: Optional[List[str]] = None + + def is_outside(self, other) -> bool: + """ + Whether this temporal range is outside the time bounds of the other temporal range + """ + return ( + self.min_temporal_partition < other.min_temporal_partition + or self.max_temporal_partition > other.max_temporal_partition + ) + + +class PartitionAvailability(TemporalPartitionRange): + """ + Partition-level availability + """ + + # This list maps to the ordered list of categorical partitions at the node level. + # For example, if the node's `categorical_partitions` are configured as ["country", "group_id"], + # a valid entry for `value` may be ["DE", null]. + value: List[Optional[str]] + + # Valid through timestamp + valid_through_ts: Optional[int] + + +class AvailabilityNode(TemporalPartitionRange): + """A node in the availability trie tracker""" + + children: Dict = {} + valid_through_ts: Optional[int] = Field(default=-sys.maxsize - 1) + + def merge_temporal(self, other: "AvailabilityNode"): + """ + Merge the temporal ranges with each other by saving the largest + possible time range. + """ + self.min_temporal_partition = min( # type: ignore + self.min_temporal_partition, + other.min_temporal_partition, + ) + self.max_temporal_partition = max( # type: ignore + self.max_temporal_partition, + other.max_temporal_partition, + ) + self.valid_through_ts = max( # type: ignore + self.valid_through_ts, + other.valid_through_ts, + ) + + +class AvailabilityTracker: + """ + Tracks the availability of the partitions in a trie, which is used for merging + availability states across categorical and temporal partitions. + """ + + def __init__(self): + self.root = AvailabilityNode() + + def insert(self, partition): + """ + Inserts the partition availability into the availability tracker trie. + """ + current = self.root + for value in partition.value: + next_item = AvailabilityNode( + min_temporal_partition=partition.min_temporal_partition, + max_temporal_partition=partition.max_temporal_partition, + valid_through_ts=partition.valid_through_ts, + ) + # If a wildcard is found, only add this specific partition's + # time range if it's wider than the range of the wildcard + if None in current.children and partition.is_outside( + current.children[None], + ): + wildcard_partition = current.children[None] + next_item.merge_temporal(wildcard_partition) + current.children[value] = next_item + else: + # Add if partition doesn't match any existing, otherwise merge with existing + if value not in current.children: + current.children[value] = next_item + else: + next_item = current.children[value] + next_item.merge_temporal(partition) + + # Remove extraneous partitions at this level if this partition value is a wildcard + if value is None: + child_keys = [key for key in current.children if key is not None] + for child in child_keys: + child_partition = current.children[child] + if not child_partition.is_outside(partition): + del current.children[child] + current = next_item + + def get_partition_range(self) -> List[PartitionAvailability]: + """ + Gets the final set of merged partitions. + """ + candidates: List[Tuple[AvailabilityNode, List[str]]] = [(self.root, [])] + final_partitions = [] + while candidates: + current, partition_list = candidates.pop() + if current.children: + for key, value in current.children.items(): + candidates.append((value, partition_list + [key])) + else: + final_partitions.append( + PartitionAvailability( + value=partition_list, + min_temporal_partition=current.min_temporal_partition, + max_temporal_partition=current.max_temporal_partition, + valid_through_ts=current.valid_through_ts, + ), + ) + return final_partitions + + +class AvailabilityStateBase(TemporalPartitionRange): + """ + An availability state base + """ + + catalog: str + schema_: Optional[str] = Field(default=None) + table: str + valid_through_ts: int + + # An ordered list of categorical partitions like ["country", "group_id"] + # or ["region_id", "age_group"] + categorical_partitions: Optional[List[str]] = Field( + sa_column=SqlaColumn("categorical_partitions", JSON), + default=[], + ) + + # An ordered list of temporal partitions like ["date", "hour"] or ["date"] + temporal_partitions: Optional[List[str]] = Field( + sa_column=SqlaColumn("temporal_partitions", JSON), + default=[], + ) + + # Node-level temporal ranges + min_temporal_partition: Optional[List[str]] = Field( + sa_column=SqlaColumn(JSON), + default=[], + ) + max_temporal_partition: Optional[List[str]] = Field( + sa_column=SqlaColumn(JSON), + default=[], + ) + + # Partition-level availabilities + partitions: Optional[List[PartitionAvailability]] = Field( + sa_column=SqlaColumn("partitions", JSON), + default=[], + ) + + @validator("partitions") + def validate_partitions(cls, partitions): # pylint: disable=no-self-argument + """ + Validator for partitions + """ + return [partition.dict() for partition in partitions] if partitions else [] + + def merge(self, other: "AvailabilityStateBase"): + """ + Merge this availability state with another. + """ + all_partitions = [ + PartitionAvailability(**partition) + if isinstance(partition, dict) + else partition + for partition in self.partitions + other.partitions # type: ignore + ] + top_level_partition = PartitionAvailability( + value=[None for _ in other.categorical_partitions] + if other.categorical_partitions + else [], + min_temporal_partition=min( + x + for x in (self.min_temporal_partition, other.min_temporal_partition) + if x + ), + max_temporal_partition=max( + x + for x in (self.max_temporal_partition, other.max_temporal_partition) + if x + ), + ) + all_partitions += [top_level_partition] + + tracker = AvailabilityTracker() + for partition in all_partitions: + tracker.insert(partition) + final_partitions = tracker.get_partition_range() + + self.partitions = [ + partition + for partition in final_partitions + if not all(val is None for val in partition.value) + ] + merged_top_level = [ + partition + for partition in final_partitions + if all(val is None for val in partition.value) + ] + + if merged_top_level: # pragma: no cover + self.min_temporal_partition = ( + top_level_partition.min_temporal_partition + or merged_top_level[0].min_temporal_partition + ) + self.max_temporal_partition = ( + top_level_partition.max_temporal_partition + or merged_top_level[0].max_temporal_partition + ) + return self + + +class AvailabilityState(AvailabilityStateBase, table=True): # type: ignore + """ + The availability of materialized data for a node + """ + + id: Optional[int] = Field(default=None, primary_key=True) + updated_at: UTCDatetime = Field( + sa_column=SqlaColumn(DateTime(timezone=True)), + default_factory=partial(datetime.now, timezone.utc), + ) + + def is_available( + self, + criteria: Optional[BuildCriteria] = None, # pylint: disable=unused-argument + ) -> bool: # pragma: no cover + """ + Determine whether an availability state is useable given criteria + """ + # Criteria to determine if an availability state should be used needs to be added + return True + + +class NodeAvailabilityState(BaseSQLModel, table=True): # type: ignore + """ + Join table for availability state + """ + + availability_id: Optional[int] = Field( + default=None, + foreign_key="availabilitystate.id", + primary_key=True, + ) + node_id: Optional[int] = Field( + default=None, + foreign_key="noderevision.id", + primary_key=True, + ) + + +class NodeNamespace(SQLModel, table=True): # type: ignore + """ + A node namespace + """ + + namespace: str = Field(nullable=False, unique=True, primary_key=True) + deactivated_at: UTCDatetime = Field( + nullable=True, + sa_column=SqlaColumn(DateTime(timezone=True)), + default=None, + ) + + +class Node(NodeBase, table=True): # type: ignore + """ + Node that acts as an umbrella for all node revisions + """ + + __table_args__ = ( + UniqueConstraint("name", "namespace", name="unique_node_namespace_name"), + ) + + id: Optional[int] = Field(default=None, primary_key=True) + namespace: Optional[str] = "default" + current_version: str = Field(default=str(DEFAULT_DRAFT_VERSION)) + created_at: UTCDatetime = Field( + sa_column=SqlaColumn(DateTime(timezone=True)), + default_factory=partial(datetime.now, timezone.utc), + ) + deactivated_at: UTCDatetime = Field( + nullable=True, + sa_column=SqlaColumn(DateTime(timezone=True)), + default=None, + ) + + revisions: List["NodeRevision"] = Relationship(back_populates="node") + current: "NodeRevision" = Relationship( + sa_relationship_kwargs={ + "primaryjoin": "and_(Node.id==NodeRevision.node_id, " + "Node.current_version == NodeRevision.version)", + "viewonly": True, + "uselist": False, + }, + ) + + children: List["NodeRevision"] = Relationship( + back_populates="parents", + link_model=NodeRelationship, + sa_relationship_kwargs={ + "primaryjoin": "Node.id==NodeRelationship.parent_id", + "secondaryjoin": "NodeRevision.id==NodeRelationship.child_id", + }, + ) + + tags: List["Tag"] = Relationship( + back_populates="nodes", + link_model=TagNodeRelationship, + sa_relationship_kwargs={ + "primaryjoin": "TagNodeRelationship.node_id==Node.id", + "secondaryjoin": "TagNodeRelationship.tag_id==Tag.id", + }, + ) + + def __hash__(self) -> int: + return hash(self.id) + + +class NodeRevision(NodeRevisionBase, table=True): # type: ignore + """ + A node revision. + """ + + __table_args__ = (UniqueConstraint("version", "node_id"),) + + id: Optional[int] = Field(default=None, primary_key=True) + version: Optional[str] = Field(default=str(DEFAULT_DRAFT_VERSION)) + node_id: Optional[int] = Field(foreign_key="node.id") + node: Node = Relationship(back_populates="revisions") + catalog_id: int = Field(default=None, foreign_key="catalog.id") + catalog: Catalog = Relationship( + back_populates="node_revisions", + sa_relationship_kwargs={ + "lazy": "joined", + }, + ) + schema_: Optional[str] = None + table: Optional[str] = None + + # A list of columns from the metric's parent that + # are required for grouping when using the metric + required_dimensions: List["Column"] = Relationship( + link_model=BoundDimensionsRelationship, + sa_relationship_kwargs={ + "primaryjoin": "NodeRevision.id==BoundDimensionsRelationship.metric_id", + "secondaryjoin": "Column.id==BoundDimensionsRelationship.bound_dimension_id", + }, + ) + + # A list of metric columns and dimension columns, only used by cube nodes + cube_elements: List["Column"] = Relationship( + link_model=CubeRelationship, + sa_relationship_kwargs={ + "primaryjoin": "NodeRevision.id==CubeRelationship.cube_id", + "secondaryjoin": "Column.id==CubeRelationship.cube_element_id", + "lazy": "joined", + }, + ) + status: NodeStatus = NodeStatus.INVALID + updated_at: UTCDatetime = Field( + sa_column=SqlaColumn(DateTime(timezone=True)), + default_factory=partial(datetime.now, timezone.utc), + ) + + parents: List["Node"] = Relationship( + back_populates="children", + link_model=NodeRelationship, + sa_relationship_kwargs={ + "primaryjoin": "NodeRevision.id==NodeRelationship.child_id", + "secondaryjoin": "Node.id==NodeRelationship.parent_id", + }, + ) + + parent_links: List[NodeRelationship] = Relationship() + + missing_parents: List[MissingParent] = Relationship( + link_model=NodeMissingParents, + sa_relationship_kwargs={ + "primaryjoin": "NodeRevision.id==NodeMissingParents.referencing_node_id", + "secondaryjoin": "MissingParent.id==NodeMissingParents.missing_parent_id", + "cascade": "all, delete", + }, + ) + + columns: List["Column"] = Relationship( + link_model=NodeColumns, + sa_relationship_kwargs={ + "primaryjoin": "NodeRevision.id==NodeColumns.node_id", + "secondaryjoin": "Column.id==NodeColumns.column_id", + "cascade": "all, delete", + }, + ) + + # The availability of materialized data needs to be stored on the NodeRevision + # level in order to support pinned versions, where a node owner wants to pin + # to a particular upstream node version. + availability: Optional[AvailabilityState] = Relationship( + link_model=NodeAvailabilityState, + sa_relationship_kwargs={ + "primaryjoin": "NodeRevision.id==NodeAvailabilityState.node_id", + "secondaryjoin": "AvailabilityState.id==NodeAvailabilityState.availability_id", + "cascade": "all, delete", + "uselist": False, + }, + ) + + # Nodes of type SOURCE will not have this property as their materialization + # is not managed as a part of this service + materializations: List[Materialization] = Relationship( + back_populates="node_revision", + sa_relationship_kwargs={ + "cascade": "all, delete-orphan", + }, + ) + + def __hash__(self) -> int: + return hash(self.id) + + def primary_key(self) -> List[Column]: + """ + Returns the primary key columns of this node. + """ + primary_key_columns = [] + for col in self.columns: # pylint: disable=not-an-iterable + if col.has_primary_key_attribute(): + primary_key_columns.append(col) + return primary_key_columns + + @staticmethod + def format_metric_alias(query: str, name: str) -> str: + """ + Metric aliases must have the same name as the node. + """ + from datajunction_server.sql.parsing import ( # pylint: disable=import-outside-toplevel + ast, + ) + from datajunction_server.sql.parsing.backends.antlr4 import ( # pylint: disable=import-outside-toplevel + parse, + ) + + tree = parse(query) + projection_0 = tree.select.projection[0] + + # if the name is not what we expect, check if it is an alias + # if it is an alias, we will raise because the user will have + # deliberately named this expression + # otherwise, we will just add the alias we want e.g. the node name + expr_name: Optional[ast.Name] = ( + projection_0.alias_or_name # type: ignore + if hasattr(projection_0, "alias") + else None + ) + if ( + expr_name + and expr_name.parent_key == "alias" + and expr_name.name != amenable_name(name) + ): + raise DJInvalidInputException( + "Invalid Metric. The expression in the projection " + "cannot have alias different from the node name. Got " + f"`{expr_name}` but expected `{amenable_name(name)}`", + ) + if not expr_name or expr_name and expr_name.parent_key != "alias": + tree.select.projection[0] = projection_0.set_alias( + ast.Name(amenable_name(name)), + ) + return str(tree) + + def check_metric(self): + """ + Check if the Node defines a metric. + + The Node SQL query should have a single expression in its + projections and it should be an aggregation function. + """ + from datajunction_server.sql.parsing.backends.antlr4 import ( # pylint: disable=import-outside-toplevel + parse, + ) + + # must have a single expression + tree = parse(self.query) + if len(tree.select.projection) != 1: + raise DJInvalidInputException( + http_status_code=HTTPStatus.BAD_REQUEST, + message="Metric queries can only have a single " + f"expression, found {len(tree.select.projection)}", + ) + projection_0 = tree.select.projection[0] + + # must have an aggregation + if ( + not hasattr(projection_0, "is_aggregation") + or not projection_0.is_aggregation() # type: ignore + ): + raise DJInvalidInputException( + http_status_code=HTTPStatus.BAD_REQUEST, + message=f"Metric {self.name} has an invalid query, " + "should have a single aggregation", + ) + + def extra_validation(self) -> None: + """ + Extra validation for node data. + """ + if self.type in (NodeType.SOURCE,): + if self.query: + raise DJInvalidInputException( + f"Node {self.name} of type {self.type} should not have a query", + ) + + if self.type in {NodeType.TRANSFORM, NodeType.METRIC, NodeType.DIMENSION}: + if not self.query: + raise DJInvalidInputException( + f"Node {self.name} of type {self.type} needs a query", + ) + + if self.type != NodeType.METRIC and self.required_dimensions: + raise DJInvalidInputException( + f"Node {self.name} of type {self.type} cannot have " + "bound dimensions which are only for metrics.", + ) + + if self.type == NodeType.METRIC: + self.check_metric() + + if self.type == NodeType.CUBE: + if not self.cube_elements: + raise DJInvalidInputException( + f"Node {self.name} of type cube node needs cube elements", + ) + + def copy_dimension_links_from_revision(self, old_revision: "NodeRevision"): + """ + Copy dimension links and attributes from another node revision if the column names match + """ + old_columns_mapping = {col.name: col for col in old_revision.columns} + for col in self.columns: # pylint: disable=not-an-iterable + if col.name in old_columns_mapping: + col.dimension_id = old_columns_mapping[col.name].dimension_id + col.attributes = old_columns_mapping[col.name].attributes or [] + return self + + class Config: # pylint: disable=missing-class-docstring,too-few-public-methods + extra = Extra.allow + + def has_available_materialization(self, build_criteria: BuildCriteria) -> bool: + """ + Has a materialization available + """ + return ( + self.availability is not None # pragma: no cover + and self.availability.is_available( # pylint: disable=no-member + criteria=build_criteria, + ) + ) + + +class ImmutableNodeFields(BaseSQLModel): + """ + Node fields that cannot be changed + """ + + name: str + namespace: str = "default" + + +class MutableNodeFields(BaseSQLModel): + """ + Node fields that can be changed. + """ + + display_name: Optional[str] + description: str + mode: NodeMode + primary_key: Optional[List[str]] + + +class MutableNodeQueryField(BaseSQLModel): + """ + Query field for node. + """ + + query: str + + +class NodeNameOutput(SQLModel): + """ + Node name only + """ + + name: str + + +class NodeNameList(SQLModel): + """ + List of node names + """ + + __root__: List[str] + + +class AttributeTypeName(BaseSQLModel): + """ + Attribute type name. + """ + + namespace: str + name: str + + +class AttributeOutput(BaseSQLModel): + """ + Column attribute output. + """ + + attribute_type: AttributeTypeName + + +class DimensionAttributeOutput(SQLModel): + """ + Dimension attribute output should include the name and type + """ + + name: str + type: ColumnType + path: List[str] + + @root_validator + def type_string(cls, values): # pylint: disable=no-self-argument + """ + Extracts the type as a string + """ + values["type"] = str(values.get("type")) + return values + + +class ColumnOutput(SQLModel): + """ + A simplified column schema, without ID or dimensions. + """ + + name: str + type: ColumnType + attributes: Optional[List[AttributeOutput]] + dimension: Optional[NodeNameOutput] + + class Config: # pylint: disable=too-few-public-methods + """ + Should perform validation on assignment + """ + + validate_assignment = True + + @root_validator + def type_string(cls, values): # pylint: disable=no-self-argument + """ + Extracts the type as a string + """ + values["type"] = str(values.get("type")) + return values + + +class SourceColumnOutput(SQLModel): + """ + A column used in creation of a source node + """ + + name: str + type: ColumnType + attributes: Optional[List[AttributeOutput]] + dimension: Optional[str] + + class Config: # pylint: disable=too-few-public-methods + """ + Should perform validation on assignment + """ + + validate_assignment = True + + @root_validator + def type_string(cls, values): # pylint: disable=no-self-argument + """ + Extracts the type as a string + """ + values["type"] = str(values.get("type")) + return values + + +class SourceNodeFields(BaseSQLModel): + """ + Source node fields that can be changed. + """ + + catalog: str + schema_: str + table: str + columns: List["SourceColumnOutput"] + + +class CubeNodeFields(BaseSQLModel): + """ + Cube node fields that can be changed + """ + + display_name: Optional[str] + metrics: List[str] + dimensions: List[str] + filters: Optional[List[str]] + orderby: Optional[List[str]] + limit: Optional[int] + description: str + mode: NodeMode + + +class MetricNodeFields(BaseSQLModel): + """ + Metric node fields that can be changed + """ + + required_dimensions: Optional[List[str]] + + +# +# Create and Update objects +# + + +class CreateNode( + ImmutableNodeFields, + MutableNodeFields, + MutableNodeQueryField, + MetricNodeFields, +): + """ + Create non-source node object. + """ + + +class CreateSourceNode(ImmutableNodeFields, MutableNodeFields, SourceNodeFields): + """ + A create object for source nodes + """ + + +class CreateCubeNode(ImmutableNodeFields, CubeNodeFields): + """ + A create object for cube nodes + """ + + +class UpdateNode(MutableNodeFields, SourceNodeFields): + """ + Update node object where all fields are optional + """ + + __annotations__ = { + k: Optional[v] + for k, v in { + **SourceNodeFields.__annotations__, # pylint: disable=E1101 + **MutableNodeFields.__annotations__, # pylint: disable=E1101 + **MutableNodeQueryField.__annotations__, # pylint: disable=E1101 + }.items() + } + + class Config: # pylint: disable=too-few-public-methods + """ + Do not allow fields other than the ones defined here. + """ + + extra = Extra.forbid + + +# +# Response output objects +# + + +class OutputModel(BaseModel): + """ + An output model with the ability to flatten fields. When fields are created with + `Field(flatten=True)`, the field's values will be automatically flattened into the + parent output model. + """ + + def _iter(self, *args, to_dict: bool = False, **kwargs): + for dict_key, value in super()._iter(to_dict, *args, **kwargs): + if to_dict and self.__fields__[dict_key].field_info.extra.get( + "flatten", + False, + ): + assert isinstance(value, dict) + for key, val in value.items(): + yield key, val + else: + yield dict_key, value + + +class TableOutput(SQLModel): + """ + Output for table information. + """ + + id: Optional[int] + catalog: Optional[Catalog] + schema_: Optional[str] + table: Optional[str] + database: Optional[Database] + + +class NodeRevisionOutput(SQLModel): + """ + Output for a node revision with information about columns and if it is a metric. + """ + + id: int = Field(alias="node_revision_id") + node_id: int + type: NodeType + name: str + display_name: str + version: str + status: NodeStatus + mode: NodeMode + catalog: Optional[Catalog] + schema_: Optional[str] + table: Optional[str] + description: str = "" + query: Optional[str] = None + availability: Optional[AvailabilityState] = None + columns: List[ColumnOutput] + updated_at: UTCDatetime + materializations: List[MaterializationConfigOutput] + parents: List[NodeNameOutput] + + class Config: # pylint: disable=missing-class-docstring,too-few-public-methods + allow_population_by_field_name = True + + +class NodeOutput(OutputModel): + """ + Output for a node that shows the current revision. + """ + + namespace: str + current: NodeRevisionOutput = PydanticField(flatten=True) + created_at: UTCDatetime + tags: List["Tag"] = [] + + +class NodeValidation(SQLModel): + """ + A validation of a provided node definition + """ + + message: str + status: NodeStatus + node_revision: NodeRevision + dependencies: List[NodeRevisionOutput] + columns: List[Column] + errors: List[DJError] + + +class LineageColumn(BaseModel): + """ + Column in lineage graph + """ + + column_name: str + node_name: Optional[str] = None + node_type: Optional[str] = None + display_name: Optional[str] = None + lineage: Optional[List["LineageColumn"]] = None + + +LineageColumn.update_forward_refs() diff --git a/datajunction-server/datajunction_server/models/query.py b/datajunction-server/datajunction_server/models/query.py new file mode 100644 index 000000000..38e3665a9 --- /dev/null +++ b/datajunction-server/datajunction_server/models/query.py @@ -0,0 +1,157 @@ +""" +Models for queries. +""" + +from datetime import datetime +from enum import Enum +from typing import Any, List, Optional + +import msgpack +from pydantic import AnyHttpUrl, validator +from sqlmodel import Field, SQLModel + +from datajunction_server.models.base import BaseSQLModel +from datajunction_server.typing import QueryState, Row + + +class BaseQuery(SQLModel): + """ + Base class for query models. + """ + + catalog_name: Optional[str] + engine_name: Optional[str] = None + engine_version: Optional[str] = None + + class Config: # pylint: disable=too-few-public-methods, missing-class-docstring + allow_population_by_field_name = True + + +class QueryCreate(BaseQuery): + """ + Model for submitted queries. + """ + + engine_name: str + engine_version: str + catalog_name: str + submitted_query: str + async_: bool = False + + +class ColumnMetadata(BaseSQLModel): + """ + A simple model for column metadata. + """ + + name: str + type: str + + +class StatementResults(BaseSQLModel): + """ + Results for a given statement. + + This contains the SQL, column names and types, and rows + """ + + sql: str + columns: List[ColumnMetadata] + rows: List[Row] + + # this indicates the total number of rows, and is useful for paginated requests + row_count: int = 0 + + +class QueryResults(BaseSQLModel): + """ + Results for a given query. + """ + + __root__: List[StatementResults] + + +class TableRef(BaseSQLModel): + """ + Table reference + """ + + catalog: str + schema_: str = Field(alias="schema") + table: str + + +class QueryWithResults(BaseSQLModel): + """ + Model for query with results. + """ + + id: str + engine_name: Optional[str] = None + engine_version: Optional[str] = None + submitted_query: str + executed_query: Optional[str] = None + + scheduled: Optional[datetime] = None + started: Optional[datetime] = None + finished: Optional[datetime] = None + + state: QueryState = QueryState.UNKNOWN + progress: float = 0.0 + + output_table: Optional[TableRef] + results: QueryResults + next: Optional[AnyHttpUrl] = None + previous: Optional[AnyHttpUrl] = None + errors: List[str] + links: Optional[List[AnyHttpUrl]] = None + + @validator("scheduled", pre=True) + def parse_scheduled_date_string(cls, value): # pylint: disable=no-self-argument + """ + Convert string date values to datetime + """ + return datetime.fromisoformat(value) if isinstance(value, str) else value + + @validator("started", pre=True) + def parse_started_date_string(cls, value): # pylint: disable=no-self-argument + """ + Convert string date values to datetime + """ + return datetime.fromisoformat(value) if isinstance(value, str) else value + + @validator("finished", pre=True) + def parse_finisheddate_string(cls, value): # pylint: disable=no-self-argument + """ + Convert string date values to datetime + """ + return datetime.fromisoformat(value) if isinstance(value, str) else value + + +class QueryExtType(int, Enum): + """ + Custom ext type for msgpack. + """ + + UUID = 1 + TIMESTAMP = 2 + + +def encode_results(obj: Any) -> Any: + """ + Custom msgpack encoder for ``QueryWithResults``. + """ + if isinstance(obj, datetime): + return msgpack.ExtType(QueryExtType.TIMESTAMP, obj.isoformat().encode("utf-8")) + + return obj + + +def decode_results(code: int, data: bytes) -> Any: + """ + Custom msgpack decoder for ``QueryWithResults``. + """ + if code == QueryExtType.TIMESTAMP: + return datetime.fromisoformat(data.decode()) + + return msgpack.ExtType(code, data) diff --git a/datajunction-server/datajunction_server/models/table.py b/datajunction-server/datajunction_server/models/table.py new file mode 100644 index 000000000..0c046e517 --- /dev/null +++ b/datajunction-server/datajunction_server/models/table.py @@ -0,0 +1,110 @@ +""" +Models for tables. +""" + +from typing import TYPE_CHECKING, List, Optional, Tuple, TypedDict + +from sqlmodel import Field, Relationship + +from datajunction_server.models.base import BaseSQLModel + +if TYPE_CHECKING: + from datajunction_server.models.catalog import Catalog + from datajunction_server.models.column import Column + from datajunction_server.models.database import Database + from datajunction_server.models.node import NodeRevision + + +class TableYAML(TypedDict, total=False): + """ + Schema of a table in the YAML file. + """ + + catalog: Optional[str] + schema: Optional[str] + table: str + cost: float + + +class TableColumns(BaseSQLModel, table=True): # type: ignore + """ + Join table for table columns. + """ + + table_id: Optional[int] = Field( + default=None, + foreign_key="table.id", + primary_key=True, + ) + column_id: Optional[int] = Field( + default=None, + foreign_key="column.id", + primary_key=True, + ) + + +class TableBase(BaseSQLModel): + """ + A base table. + """ + + schema_: Optional[str] = Field(default=None, alias="schema") + table: str + cost: float = 1.0 + + +class Table(TableBase, table=True): # type: ignore + """ + A table with data. + + Nodes can have data in multiple tables, in different databases. + """ + + id: Optional[int] = Field(default=None, primary_key=True) + + database_id: int = Field(foreign_key="database.id") + database: "Database" = Relationship(back_populates="tables") + + columns: List["Column"] = Relationship( + link_model=TableColumns, + sa_relationship_kwargs={ + "primaryjoin": "Table.id==TableColumns.table_id", + "secondaryjoin": "Column.id==TableColumns.column_id", + "cascade": "all, delete", + }, + ) + + def identifier( + self, + ) -> Tuple[Optional[str], Optional[str], str]: # pragma: no cover + """ + Unique identifier for this table. + """ + # Catalogs will soon be required and this return can be simplified + return ( + self.catalog.name if self.catalog else None, # pylint: disable=no-member + self.schema_, + self.table, + ) + + def __hash__(self): + return hash(self.id) + + +class CreateColumn(BaseSQLModel): + """ + A column creation request + """ + + name: str + type: str + + +class CreateTable(TableBase): + """ + Create table input + """ + + database_name: str + catalog_name: str + columns: List[CreateColumn] diff --git a/datajunction-server/datajunction_server/models/tag.py b/datajunction-server/datajunction_server/models/tag.py new file mode 100644 index 000000000..ccf4bad6c --- /dev/null +++ b/datajunction-server/datajunction_server/models/tag.py @@ -0,0 +1,111 @@ +""" +Models for tags. +""" +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +from pydantic import Extra +from sqlalchemy import String +from sqlalchemy.sql.schema import Column as SqlaColumn +from sqlmodel import JSON, Field, Relationship + +from datajunction_server.models.base import BaseSQLModel, generate_display_name + +if TYPE_CHECKING: + from datajunction_server.models.node import Node + + +class MutableTagFields(BaseSQLModel): + """ + Tag fields that can be changed. + """ + + description: str + tag_metadata: Dict[str, Any] = Field(default={}, sa_column=SqlaColumn(JSON)) + + class Config: # pylint: disable=too-few-public-methods + """ + Allow types for tag metadata. + """ + + arbitrary_types_allowed = True + + +class ImmutableTagFields(BaseSQLModel): + """ + Tag fields that cannot be changed. + """ + + name: str = Field(sa_column=SqlaColumn("name", String, unique=True)) + display_name: Optional[str] = Field( + sa_column=SqlaColumn( + "display_name", + String, + default=generate_display_name("name"), + ), + ) + tag_type: str + + +class TagNodeRelationship(BaseSQLModel, table=True): # type: ignore + """ + Join table between tags and nodes + """ + + tag_id: Optional[int] = Field( + default=None, + foreign_key="tag.id", + primary_key=True, + ) + node_id: Optional[int] = Field( + default=None, + foreign_key="node.id", + primary_key=True, + ) + + +class Tag(ImmutableTagFields, MutableTagFields, table=True): # type: ignore + """ + A tag. + """ + + id: Optional[int] = Field(default=None, primary_key=True) + nodes: List["Node"] = Relationship( + back_populates="tags", + link_model=TagNodeRelationship, + sa_relationship_kwargs={ + "primaryjoin": "TagNodeRelationship.tag_id==Tag.id", + "secondaryjoin": "TagNodeRelationship.node_id==Node.id", + }, + ) + + +class CreateTag(ImmutableTagFields, MutableTagFields): + """ + Create tag model. + """ + + +class TagOutput(ImmutableTagFields, MutableTagFields): + """ + Output tag model. + """ + + +class UpdateTag(MutableTagFields): + """ + Update tag model. Only works on mutable fields. + """ + + __annotations__ = { + k: Optional[v] + for k, v in { + **MutableTagFields.__annotations__, # pylint: disable=E1101 + }.items() + } + + class Config: # pylint: disable=too-few-public-methods + """ + Do not allow fields other than the ones defined here. + """ + + extra = Extra.forbid diff --git a/datajunction-server/datajunction_server/models/user.py b/datajunction-server/datajunction_server/models/user.py new file mode 100644 index 000000000..cf1415f91 --- /dev/null +++ b/datajunction-server/datajunction_server/models/user.py @@ -0,0 +1,42 @@ +""" +Models for users and auth +""" +from enum import Enum +from typing import Optional + +from pydantic import BaseModel +from sqlmodel import Field, SQLModel + + +class OAuthProvider(Enum): + """ + Support oauth providers + """ + + BASIC = "basic" + GITHUB = "github" + + +class User(SQLModel, table=True): # type: ignore + """Class for a user.""" + + __tablename__ = "users" + + id: Optional[int] = Field(default=None, primary_key=True) + username: str + password: Optional[str] + email: Optional[str] + name: Optional[str] + oauth_provider: OAuthProvider + is_admin: bool = False + + +class UserOutput(BaseModel): + """User information to be included in responses""" + + id: Optional[int] = Field(default=None, primary_key=True) + username: str + email: Optional[str] + name: Optional[str] + oauth_provider: OAuthProvider + is_admin: bool = False diff --git a/datajunction-server/datajunction_server/service_clients.py b/datajunction-server/datajunction_server/service_clients.py new file mode 100644 index 000000000..6a7b1143f --- /dev/null +++ b/datajunction-server/datajunction_server/service_clients.py @@ -0,0 +1,193 @@ +"""Clients for various configurable services.""" +from typing import TYPE_CHECKING, List, Optional, Union +from urllib.parse import urljoin + +import requests +from requests.adapters import HTTPAdapter +from urllib3 import Retry + +from datajunction_server.errors import DJQueryServiceClientException +from datajunction_server.models.column import Column +from datajunction_server.models.materialization import ( + DruidMaterializationInput, + GenericMaterializationInput, + MaterializationInfo, +) +from datajunction_server.models.query import QueryCreate, QueryWithResults +from datajunction_server.sql.parsing.types import ColumnType + +if TYPE_CHECKING: + from datajunction_server.models.engine import Engine + + +class RequestsSessionWithEndpoint(requests.Session): + """ + Creates a requests session that comes with an endpoint that all + subsequent requests will use as a prefix. + """ + + def __init__(self, endpoint: str = None, retry_strategy: Retry = None): + super().__init__() + self.endpoint = endpoint + self.mount("http://", HTTPAdapter(max_retries=retry_strategy)) + self.mount("https://", HTTPAdapter(max_retries=retry_strategy)) + + def request(self, method, url, *args, **kwargs): + """ + Make the request with the full URL. + """ + url = self.construct_url(url) + return super().request(method, url, *args, **kwargs) + + def prepare_request(self, request, *args, **kwargs): + """ + Prepare the request with the full URL. + """ + request.url = self.construct_url(request.url) + return super().prepare_request( + request, + *args, + **kwargs, + ) + + def construct_url(self, url): + """ + Construct full URL based off the endpoint. + """ + return urljoin(self.endpoint, url) + + +class QueryServiceClient: # pylint: disable=too-few-public-methods + """ + Client for the query service. + """ + + def __init__(self, uri: str, retries: int = 0): + self.uri = uri + retry_strategy = Retry( + total=retries, + backoff_factor=1.5, + status_forcelist=[429, 500, 502, 503, 504], + allowed_methods=["GET", "POST", "PUT", "PATCH"], + ) + self.requests_session = RequestsSessionWithEndpoint( + endpoint=self.uri, + retry_strategy=retry_strategy, + ) + + def get_columns_for_table( + self, + catalog: str, + schema: str, + table: str, + engine: Optional["Engine"] = None, + ) -> List[Column]: + """ + Retrieves columns for a table. + """ + response = self.requests_session.get( + f"/table/{catalog}.{schema}.{table}/columns/", + params={ + "engine": engine.name, + "engine_version": engine.version, + } + if engine + else {}, + ) + table_columns = response.json()["columns"] + return [ + Column(name=column["name"], type=ColumnType(column["type"])) + for column in table_columns + ] + + def submit_query( # pylint: disable=too-many-arguments + self, + query_create: QueryCreate, + ) -> QueryWithResults: + """ + Submit a query to the query service + """ + response = self.requests_session.post( + "/queries/", + json=query_create.dict(), + ) + response_data = response.json() + if not response.ok: + raise DJQueryServiceClientException( + message=f"Error response from query service: {response_data['message']}", + ) + query_info = response.json() + return QueryWithResults(**query_info) + + def get_query( + self, + query_id: str, + ) -> QueryWithResults: + """ + Get a previously submitted query + """ + response = self.requests_session.get(f"/queries/{query_id}/") + if not response.ok: + raise DJQueryServiceClientException( + message=f"Error response from query service: {response.text}", + ) + query_info = response.json() + return QueryWithResults(**query_info) + + def materialize( # pylint: disable=too-many-arguments + self, + materialization_input: Union[ + GenericMaterializationInput, + DruidMaterializationInput, + ], + ) -> MaterializationInfo: + """ + Post a request to the query service asking it to set up a scheduled materialization + for the node. The query service is expected to manage all reruns of this job. Note + that this functionality may be moved to the materialization service at a later point. + """ + response = self.requests_session.post( + "/materialization/", + json=materialization_input.dict(), + ) + if not response.ok: # pragma: no cover + return MaterializationInfo(urls=[], output_tables=[]) + result = response.json() + return MaterializationInfo(**result) + + def deactivate_materialization( + self, + node_name: str, + materialization_name: str, + ) -> MaterializationInfo: + """ + Deactivates the specified node materialization + """ + response = self.requests_session.delete( + "/materialization/", + params={ + "node_name": node_name, + "materialization_name": materialization_name, + }, + ) + if not response.ok: # pragma: no cover + return MaterializationInfo(urls=[], output_tables=[]) + result = response.json() + return MaterializationInfo(**result) + + def get_materialization_info( + self, + node_name: str, + node_version: str, + materialization_name: str, + ) -> MaterializationInfo: + """ + Gets materialization info for the node and materialization config name. + """ + response = self.requests_session.get( + f"/materialization/{node_name}/{node_version}/{materialization_name}/", + timeout=3, + ) + if not response.ok: + return MaterializationInfo(output_tables=[], urls=[]) + return MaterializationInfo(**response.json()) diff --git a/datajunction-server/datajunction_server/sql/__init__.py b/datajunction-server/datajunction_server/sql/__init__.py new file mode 100644 index 000000000..008fb504a --- /dev/null +++ b/datajunction-server/datajunction_server/sql/__init__.py @@ -0,0 +1,3 @@ +""" +Common dj sql imports +""" diff --git a/datajunction-server/datajunction_server/sql/dag.py b/datajunction-server/datajunction_server/sql/dag.py new file mode 100644 index 000000000..87fe4cdcb --- /dev/null +++ b/datajunction-server/datajunction_server/sql/dag.py @@ -0,0 +1,235 @@ +""" +DAG related functions. +""" +import collections +import itertools +from typing import Deque, Dict, List, Optional, Set, Tuple, Union + +from sqlmodel import Session, select + +from datajunction_server.models import Column +from datajunction_server.models.base import NodeColumns +from datajunction_server.models.node import ( + DimensionAttributeOutput, + Node, + NodeRevision, + NodeType, +) +from datajunction_server.utils import get_settings + +settings = get_settings() + + +def get_dimensions( + node: Node, + attributes: bool = True, +) -> List[Union[DimensionAttributeOutput, Node]]: + """ + Return all available dimensions for a given node. + * Setting `attributes` to True will return a list of dimension attributes, + * Setting `attributes` to False will return a list of dimension nodes + """ + dimensions = [] + + # Start with the node itself or the node's immediate parent if it's a metric node + StateTrackingType = Tuple[Node, List[Column]] + node_starting_state: StateTrackingType = (node, []) + immediate_parent_starting_state: List[StateTrackingType] = [ + (parent, []) + for parent in (node.current.parents if node.type == NodeType.METRIC else []) + ] + to_process: Deque[StateTrackingType] = collections.deque( + [node_starting_state, *immediate_parent_starting_state], + ) + processed: Set[Node] = set() + + while to_process: + current_node, join_path = to_process.popleft() + + # Don't include attributes from deactivated dimensions + if current_node.deactivated_at: + continue + processed.add(current_node) + + for column in current_node.current.columns: + # Include the dimension if it's a column belonging to a dimension node + # or if it's tagged with the dimension column attribute + if ( + current_node.type == NodeType.DIMENSION + or any( + attr.attribute_type.name == "dimension" + for attr in column.attributes + ) + or column.dimension + ): + join_path_str = [ + ( + ( + link_column.node_revisions[0].name + "." + if link_column.node_revisions + else "" + ) + + link_column.name + ) + for link_column in join_path + if link_column is not None and link_column.dimension + ] + dimensions.append( + DimensionAttributeOutput( + name=f"{current_node.name}.{column.name}", + type=column.type, + path=join_path_str, + ), + ) + if column.dimension and column.dimension not in processed: + to_process.append((column.dimension, join_path + [column])) + if attributes: + return sorted(dimensions, key=lambda x: x.name) + return sorted(list(processed), key=lambda x: x.name) + + +def check_convergence(path1: List[str], path2: List[str]) -> bool: + """ + Determines whether two join paths converge before we reach the + final element, the dimension attribute. + """ + if path1 == path2: + return True + len1 = len(path1) + len2 = len(path2) + min_len = min(len1, len2) + + for i in range(min_len): + partial1 = path1[len1 - i - 1 :] + partial2 = path2[len2 - i - 1 :] + if partial1 == partial2: + return True + + return False + + +def group_dimensions_by_name(node: Node) -> Dict[str, List[DimensionAttributeOutput]]: + """ + Group the dimensions for the node by the dimension attribute name + """ + return { + k: list(v) + for k, v in itertools.groupby( + get_dimensions(node), + key=lambda dim: dim.name, + ) + } + + +def get_shared_dimensions( + metric_nodes: List[Node], +) -> List[DimensionAttributeOutput]: + """ + Return a list of dimensions that are common between the nodes. + """ + common = group_dimensions_by_name(metric_nodes[0]) + for node in set(metric_nodes[1:]): + node_dimensions = group_dimensions_by_name(node) + + # Merge each set of dimensions based on the name and path + to_delete = set() + common_dim_keys = common.keys() & list(node_dimensions.keys()) + if not common_dim_keys: + return [] + for common_dim in common_dim_keys: + for existing_attr in common[common_dim]: + for new_attr in node_dimensions[common_dim]: + converged = check_convergence(existing_attr.path, new_attr.path) + if not converged: + to_delete.add(common_dim) + + for dim_key in to_delete: + del common[dim_key] + + return sorted( + [y for x in common.values() for y in x], + key=lambda x: (x.name, x.path), + ) + + +def get_nodes_with_dimension( + session: Session, + dimension_node: Node, + node_types: Optional[List[NodeType]] = None, +) -> List[NodeRevision]: + """ + Find all nodes that can be joined to a given dimension + """ + to_process = [dimension_node] + processed: Set[str] = set() + final_set: Set[NodeRevision] = set() + while to_process: + current_node = to_process.pop() + processed.add(current_node.name) + + # Dimension nodes are used to expand the searchable graph by finding + # the next layer of nodes that are linked to this dimension + if current_node.type == NodeType.DIMENSION: + statement = ( + select(NodeRevision) + .join( + Node, + onclause=( + (NodeRevision.node_id == Node.id) + & (Node.current_version == NodeRevision.version) + ), # pylint: disable=superfluous-parens + ) + .join( + NodeColumns, + onclause=( + NodeRevision.id == NodeColumns.node_id + ), # pylint: disable=superfluous-parens + ) + .join( + Column, + onclause=( + NodeColumns.column_id == Column.id + ), # pylint: disable=superfluous-parens + ) + .where( + Column.dimension_id.in_( # type: ignore # pylint: disable=no-member + [current_node.id], + ), + ) + ) + node_revisions = session.exec(statement).unique().all() + for node_rev in node_revisions: + to_process.append(node_rev.node) + else: + # All other nodes are added to the result set + final_set.add(current_node.current) + for child in current_node.children: + if child.name not in processed: + to_process.append(child.node) + if node_types: + return [node for node in final_set if node.type in node_types] + return list(final_set) + + +def get_nodes_with_common_dimensions( + session: Session, + common_dimensions: List[Node], + node_types: Optional[List[NodeType]] = None, +) -> List[NodeRevision]: + """ + Find all nodes that share a list of common dimensions + """ + nodes_that_share_dimensions = set() + first = True + for dimension in common_dimensions: + new_nodes = get_nodes_with_dimension(session, dimension, node_types) + if first: + nodes_that_share_dimensions = set(new_nodes) + first = False + else: + nodes_that_share_dimensions = nodes_that_share_dimensions.intersection( + set(new_nodes), + ) + if not nodes_that_share_dimensions: + break + return list(nodes_that_share_dimensions) diff --git a/datajunction-server/datajunction_server/sql/functions.py b/datajunction-server/datajunction_server/sql/functions.py new file mode 100644 index 000000000..31cff1ccd --- /dev/null +++ b/datajunction-server/datajunction_server/sql/functions.py @@ -0,0 +1,3795 @@ +# pylint: disable=too-many-lines, abstract-method, unused-argument, missing-function-docstring, +# pylint: disable=arguments-differ, too-many-return-statements, function-redefined +# mypy: ignore-errors + +""" +SQL functions for type inference. + +This file holds all the functions that we want to support in the SQL used to define +nodes. The functions are used to infer types. + +Spark function reference +https://github.com/apache/spark/tree/74cddcfda3ac4779de80696cdae2ba64d53fc635/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions + +Java strictmath reference +https://docs.oracle.com/javase/8/docs/api/java/lang/StrictMath.html + +Databricks reference: +https://docs.databricks.com/sql/language-manual/sql-ref-functions-builtin-alpha.html +""" + +import inspect +import re +from itertools import zip_longest +from typing import ( + TYPE_CHECKING, + Callable, + ClassVar, + Dict, + List, + Optional, + Tuple, + Type, + Union, + cast, + get_origin, +) + +import datajunction_server.sql.parsing.types as ct +from datajunction_server.errors import ( + DJError, + DJInvalidInputException, + DJNotImplementedException, + ErrorCode, +) +from datajunction_server.sql.parsing.backends.exceptions import DJParseException +from datajunction_server.utils import get_settings + +if TYPE_CHECKING: + from datajunction_server.sql.parsing.ast import Expression + + +def compare_registers(types, register) -> bool: + """ + Comparing registers + """ + for (type_a, register_a), (type_b, register_b) in zip_longest( + types, + register, + fillvalue=(-1, None), + ): + if type_b == -1 and register_b is None: + if register[-1][0] == -1: # args + register_b = register[-1][1] + else: + return False # pragma: no cover + if type_a == -1: + register_a = type(register_a) + if not issubclass(register_a, register_b): # type: ignore + return False + return True + + +class DispatchMeta(type): + """ + Dispatch abstract class for function registry + """ + + def __getattribute__(cls, func_name): # pylint: disable=redefined-outer-name + if func_name in type.__getattribute__(cls, "registry").get(cls, {}): + + def dynamic_dispatch(*args: "Expression"): + return cls.dispatch(func_name, *args)(*args) + + return dynamic_dispatch + return type.__getattribute__(cls, func_name) + + +class Dispatch(metaclass=DispatchMeta): + """ + Function registry + """ + + registry: ClassVar[Dict[str, Dict[Tuple[Tuple[int, Type]], Callable]]] = {} + + @classmethod + def register(cls, func): # pylint: disable=redefined-outer-name + func_name = func.__name__ + params = inspect.signature(func).parameters + spread_types = [[]] + cls.registry[cls] = cls.registry.get(cls) or {} + cls.registry[cls][func_name] = cls.registry[cls].get(func_name) or {} + for i, (key, value) in enumerate(params.items()): + name = str(value).split(":", maxsplit=1)[0] + if name.startswith("**"): + raise ValueError( + "kwargs are not supported in dispatch.", + ) # pragma: no cover + if name.startswith("*"): + i = -1 + type_ = params[key].annotation + if type_ == inspect.Parameter.empty: + raise ValueError( # pragma: no cover + "All arguments must have a type annotation.", + ) + inner_types = [type_] + if get_origin(type_) == Union: + inner_types = type_.__args__ + for _ in inner_types: + spread_types += spread_types[:] + temp = [] + for type_ in inner_types: + for types in spread_types: + temp.append(types[:]) + temp[-1].append((i, type_)) + spread_types = temp + for types in spread_types: + cls.registry[cls][func_name][tuple(types)] = func # type: ignore + return func + + @classmethod + def dispatch( # pylint: disable=redefined-outer-name + cls, func_name, *args: "Expression" + ): + type_registry = cls.registry[cls].get(func_name) # type: ignore + if not type_registry: + raise ValueError( + f"No function registered on {cls.__name__}`{func_name}`.", + ) # pragma: no cover + + type_list = [] + for i, arg in enumerate(args): + type_list.append((i, type(arg.type) if hasattr(arg, "type") else type(arg))) + + types = tuple(type_list) + + if types in type_registry: # type: ignore + return type_registry[types] # type: ignore + + for register, func in type_registry.items(): # type: ignore + if compare_registers(types, register): + return func + + raise TypeError( + f"`{cls.__name__}.{func_name}` got an invalid " + "combination of types: " + f'{", ".join(str(t[1].__name__) for t in types)}', + ) + + +class Function(Dispatch): # pylint: disable=too-few-public-methods + """ + A DJ function. + """ + + is_aggregation: ClassVar[bool] = False + is_runtime: ClassVar[bool] = False + + @staticmethod + def infer_type(*args) -> ct.ColumnType: + raise NotImplementedError() + + @staticmethod + def compile_lambda(*args): + """ + Compiles the lambda function used by a given Spark function that takes + a lambda function. This allows us to evaluate the lambda's expression and + determine the result's type. + """ + + +class TableFunction(Dispatch): # pylint: disable=too-few-public-methods + """ + A DJ table-valued function. + """ + + @staticmethod + def infer_type(*args) -> List[ct.ColumnType]: + raise NotImplementedError() + + +class DjLogicalTimestamp(Function): + """ + A special function that returns the "current" timestamp as a string based on the + specified format. Used for incrementally materializing nodes, where "current" refers + to the timestamp associated with the given partition that's being processed. + """ + + is_runtime = True + + @staticmethod + def substitute(): + settings = get_settings() + return settings.dj_logical_timestamp_format + + +@DjLogicalTimestamp.register # type: ignore +def infer_type() -> ct.StringType: + """ + Defaults to returning a timestamp in the format %Y-%m-%d %H:%M:%S + """ + return ct.StringType() + + +@DjLogicalTimestamp.register # type: ignore +def infer_type(_: ct.StringType) -> ct.StringType: + """ + This function can optionally take a datetime format string like: + DJ_CURRENT_TIMESTAMP('%Y-%m-%d') + """ + return ct.StringType() + + +##################### +# Regular Functions # +##################### + + +class Abs(Function): + """ + Returns the absolute value of the numeric or interval value. + """ + + is_aggregation = True + + +@Abs.register +def infer_type( + arg: ct.NumberType, +) -> ct.NumberType: + type_ = arg.type + return type_ + + +class Aggregate(Function): + """ + Applies a binary operator to an initial state and all elements in the array, + and reduces this to a single state. The final state is converted into the + final result by applying a finish function. + """ + + @staticmethod + def compile_lambda(*args): + """ + Compiles the lambda function used by the `aggregate` Spark function so that + the lambda's expression can be evaluated to determine the result's type. + """ + from datajunction_server.sql.parsing import ( # pylint: disable=import-outside-toplevel + ast, + ) + + expr, start, merge = args + available_identifiers = { + identifier.name: idx for idx, identifier in enumerate(merge.identifiers) + } + columns = list( + merge.expr.filter( + lambda x: isinstance(x, ast.Column) + and x.alias_or_name.name in available_identifiers, + ), + ) + for col in columns: + if available_identifiers.get(col.alias_or_name.name) == 0: + col.add_type(start.type) + if available_identifiers.get(col.alias_or_name.name) == 1: + col.add_type(expr.type.element.type) + + +@Aggregate.register # type: ignore +def infer_type( + expr: ct.ListType, + start: ct.PrimitiveType, + merge: ct.PrimitiveType, +) -> ct.ColumnType: + return merge.expr.type + + +class ApproxPercentile(Function): + """ + approx_percentile(col, percentage [, accuracy]) - + Returns the approximate percentile of the numeric or ansi interval + column col which is the smallest value in the ordered col values + """ + + is_aggregation = True + + +@ApproxPercentile.register +def infer_type( + col: ct.NumberType, + percentage: ct.ListType, + accuracy: Optional[ct.NumberType], +) -> ct.DoubleType: + return ct.ListType(element_type=col.type) # type: ignore + + +@ApproxPercentile.register +def infer_type( + col: ct.NumberType, + percentage: ct.FloatType, + accuracy: Optional[ct.NumberType], +) -> ct.NumberType: + return col.type # type: ignore + + +class Array(Function): + """ + Returns an array of constants + """ + + +@Array.register # type: ignore +def infer_type( + *elements: ct.ColumnType, +) -> ct.ListType: + types = {element.type for element in elements if element.type != ct.NullType()} + if len(types) > 1: + raise DJParseException( + f"Multiple types {', '.join(sorted(str(typ) for typ in types))} passed to array.", + ) + element_type = elements[0].type if elements else ct.NullType() + return ct.ListType(element_type=element_type) + + +@Array.register # type: ignore +def infer_type() -> ct.ListType: + return ct.ListType(element_type=ct.NullType()) + + +class ArrayAgg(Function): + """ + Collects and returns a list of non-unique elements. + """ + + +@ArrayAgg.register # type: ignore +def infer_type( + *elements: ct.ColumnType, +) -> ct.ListType: + types = {element.type for element in elements} + if len(types) > 1: # pragma: no cover + raise DJParseException( + f"Multiple types {', '.join(sorted(str(typ) for typ in types))} passed to array.", + ) + element_type = elements[0].type if elements else ct.NullType() + return ct.ListType(element_type=element_type) + + +class ArrayAppend(Function): + """ + Add the element at the end of the array passed as first argument + """ + + +@ArrayAppend.register # type: ignore +def infer_type( + array: ct.ListType, + item: ct.ColumnType, +) -> ct.ListType: + return ct.ListType(element_type=item.type) + + +class ArrayCompact(Function): + """ + array_compact(array) - Removes null values from the array. + """ + + +@ArrayCompact.register +def infer_type( + array: ct.ListType, +) -> ct.ListType: + return array.type + + +class ArrayContains(Function): + """ + array_contains(array, value) - Returns true if the array contains the value. + """ + + +@ArrayContains.register +def infer_type( + array: ct.ListType, + element: ct.ColumnType, +) -> ct.BooleanType: + return ct.BooleanType() + + +class ArrayDistinct(Function): + """ + array_distinct(array) - Removes duplicate values from the array. + """ + + +@ArrayDistinct.register +def infer_type( + array: ct.ListType, +) -> ct.ListType: + return array.type + + +class ArrayExcept(Function): + """ + array_except(array1, array2) - Returns an array of the elements in + array1 but not in array2, without duplicates. + """ + + +@ArrayExcept.register +def infer_type( + array1: ct.ListType, + array2: ct.ListType, +) -> ct.ListType: + return array1.type + + +class ArrayIntersect(Function): + """ + array_intersect(array1, array2) - Returns an array of the + elements in the intersection of array1 and array2, without duplicates. + """ + + +@ArrayIntersect.register +def infer_type( + array1: ct.ListType, + array2: ct.ListType, +) -> ct.ListType: + return array1.type + + +class ArrayJoin(Function): + """ + array_join(array, delimiter[, nullReplacement]) - Concatenates + the elements of the given array using the delimiter and an + optional string to replace nulls. + """ + + +@ArrayJoin.register +def infer_type( + array: ct.ListType, + delimiter: ct.StringType, +) -> ct.StringType: + return ct.StringType() + + +@ArrayJoin.register +def infer_type( + array: ct.ListType, + delimiter: ct.StringType, + null_replacement: ct.StringType, +) -> ct.StringType: + return ct.StringType() + + +class ArrayMax(Function): + """ + array_max(array) - Returns the maximum value in the array. NaN is + greater than any non-NaN elements for double/float type. NULL + elements are skipped. + """ + + +@ArrayMax.register +def infer_type( + array: ct.ListType, +) -> ct.NumberType: + return array.type.element.type + + +class ArrayMin(Function): + """ + array_min(array) - Returns the minimum value in the array. NaN is greater than + any non-NaN elements for double/float type. NULL elements are skipped. + """ + + +@ArrayMin.register +def infer_type( + array: ct.ListType, +) -> ct.NumberType: + return array.type.element.type + + +class ArrayPosition(Function): + """ + array_position(array, element) - Returns the (1-based) index of the first + element of the array as long. + """ + + +@ArrayPosition.register +def infer_type( + array: ct.ListType, + element: ct.ColumnType, +) -> ct.LongType: + return ct.LongType() + + +class ArrayRemove(Function): + """ + array_remove(array, element) - Remove all elements that equal to element from array. + """ + + +@ArrayRemove.register +def infer_type( + array: ct.ListType, + element: ct.ColumnType, +) -> ct.ListType: + return array.type + + +class ArrayRepeat(Function): + """ + array_repeat(element, count) - Returns the array containing element count times. + """ + + +@ArrayRepeat.register +def infer_type( + element: ct.ColumnType, + count: ct.IntegerType, +) -> ct.ListType: + return ct.ListType(element_type=element.type) + + +class ArraySize(Function): + """ + array_size(expr) - Returns the size of an array. The function returns null for null input. + """ + + +@ArraySize.register +def infer_type( + array: ct.ListType, +) -> ct.LongType: + return ct.LongType() + + +class ArraySort(Function): + """ + array_sort(expr, func) - Sorts the input array + """ + + +@ArraySort.register +def infer_type( + array: ct.ListType, +) -> ct.ListType: + return array.type + + +@ArraySort.register +def infer_type( + array: ct.ListType, + sort_func: ct.LambdaType, +) -> ct.ListType: # pragma: no cover + return array.type + + +class ArrayUnion(Function): + """ + array_union(array1, array2) - Returns an array of the elements + in the union of array1 and array2, without duplicates. + """ + + +@ArrayUnion.register +def infer_type( + array1: ct.ListType, + array2: ct.ListType, +) -> ct.ListType: + return array1.type + + +class ArraysOverlap(Function): + """ + arrays_overlap(a1, a2) - Returns true if a1 contains at least a + non-null element present also in a2. + """ + + +@ArraysOverlap.register +def infer_type( + array1: ct.ListType, + array2: ct.ListType, +) -> ct.ListType: + return ct.BooleanType() + + +class Avg(Function): + """ + Computes the average of the input column or expression. + """ + + is_aggregation = True + + +@Avg.register +def infer_type( + arg: ct.DecimalType, +) -> ct.DecimalType: + type_ = arg.type + return ct.DecimalType(type_.precision + 4, type_.scale + 4) + + +@Avg.register +def infer_type( + arg: ct.IntervalTypeBase, +) -> ct.IntervalTypeBase: + return type(arg.type)() + + +@Avg.register # type: ignore +def infer_type( + arg: ct.NumberType, +) -> ct.DoubleType: + return ct.DoubleType() + + +@Avg.register # type: ignore +def infer_type( + arg: ct.DateTimeBase, +) -> ct.DateTimeBase: + return type(arg.type)() + + +class Cardinality(Function): + """ + Returns the size of an array or a map. + """ + + +@Cardinality.register # type: ignore +def infer_type( + args: ct.ListType, +) -> ct.IntegerType: + return ct.IntegerType() + + +@Cardinality.register # type: ignore +def infer_type( + args: ct.MapType, +) -> ct.IntegerType: + return ct.IntegerType() + + +class Cbrt(Function): + """ + cbrt(expr) - Computes the cube root of the value expr. + """ + + +@Cbrt.register # type: ignore +def infer_type( + arg: ct.NumberType, +) -> ct.ColumnType: + return ct.FloatType() + + +class Ceil(Function): + """ + Computes the smallest integer greater than or equal to the input value. + """ + + +class Ceiling(Function): + """ + ceiling(expr[, scale]) - Returns the smallest number after rounding up that is not smaller + than expr. An optional scale parameter can be specified to control the rounding behavior. + """ + + +@Ceil.register +@Ceiling.register +def infer_type( + args: ct.NumberType, + _target_scale: ct.IntegerType, +) -> ct.DecimalType: + target_scale = _target_scale.value + if isinstance(args.type, ct.DecimalType): + precision = max(args.type.precision - args.type.scale + 1, -target_scale + 1) + scale = min(args.type.scale, max(0, target_scale)) + return ct.DecimalType(precision, scale) + if args.type == ct.TinyIntType(): + precision = max(3, -target_scale + 1) + return ct.DecimalType(precision, 0) + if args.type == ct.SmallIntType(): + precision = max(5, -target_scale + 1) + return ct.DecimalType(precision, 0) + if args.type == ct.IntegerType(): + precision = max(10, -target_scale + 1) + return ct.DecimalType(precision, 0) + if args.type == ct.BigIntType(): + precision = max(20, -target_scale + 1) + return ct.DecimalType(precision, 0) + if args.type == ct.FloatType(): + precision = max(14, -target_scale + 1) + scale = min(7, max(0, target_scale)) + return ct.DecimalType(precision, scale) + if args.type == ct.DoubleType(): + precision = max(30, -target_scale + 1) + scale = min(15, max(0, target_scale)) + return ct.DecimalType(precision, scale) + + raise DJParseException( # pragma: no cover + f"Unhandled numeric type in Ceil `{args.type}`", + ) + + +@Ceil.register +@Ceiling.register +def infer_type( + args: ct.DecimalType, +) -> ct.DecimalType: + return ct.DecimalType(args.type.precision - args.type.scale + 1, 0) + + +@Ceil.register +@Ceiling.register +def infer_type( + args: ct.NumberType, +) -> ct.BigIntType: + return ct.BigIntType() + + +class Char(Function): + """ + char(expr) - Returns the ASCII character having the binary equivalent to expr. + """ + + +@Char.register # type: ignore +def infer_type( + arg: ct.IntegerType, +) -> ct.ColumnType: + return ct.StringType() + + +class CharLength(Function): + """ + char_length(expr) - Returns the length of the value expr. + """ + + +@CharLength.register # type: ignore +def infer_type(arg: ct.StringType) -> ct.ColumnType: + return ct.IntegerType() + + +class CharacterLength(Function): + """ + character_length(expr) - Returns the length of the value expr. + """ + + +@CharacterLength.register # type: ignore +def infer_type(arg: ct.StringType) -> ct.ColumnType: + return ct.IntegerType() + + +class Chr(Function): + """ + chr(expr) - Returns the ASCII character having the binary equivalent to expr. + """ + + +@Chr.register # type: ignore +def infer_type(arg: ct.IntegerType) -> ct.ColumnType: + return ct.StringType() + + +class Coalesce(Function): + """ + Computes the average of the input column or expression. + """ + + is_aggregation = False + + +@Coalesce.register # type: ignore +def infer_type( + *args: ct.ColumnType, +) -> ct.ColumnType: + if not args: # pragma: no cover + raise DJInvalidInputException( + message="Wrong number of arguments to function", + errors=[ + DJError( + code=ErrorCode.INVALID_ARGUMENTS_TO_FUNCTION, + message="You need to pass at least one argument to `COALESCE`.", + ), + ], + ) + for arg in args: + if arg.type != ct.NullType(): + return arg.type + return ct.NullType() + + +class CollectList(Function): + """ + Collects and returns a list of non-unique elements. + """ + + is_aggregation = True + + +@CollectList.register +def infer_type( + arg: ct.ColumnType, +) -> ct.ColumnType: + return ct.ListType(element_type=arg.type) + + +class CollectSet(Function): + """ + Collects and returns a list of unique elements. + """ + + is_aggregation = True + + +@CollectSet.register +def infer_type( + arg: ct.ColumnType, +) -> ct.ColumnType: + return ct.ListType(element_type=arg.type) + + +class Concat(Function): + """ + concat(col1, col2, ..., colN) - Returns the concatenation of col1, col2, ..., colN. + """ + + +@Concat.register # type: ignore +def infer_type( + *strings: ct.StringType, +) -> ct.StringType: + return ct.StringType() + + +@Concat.register # type: ignore +def infer_type( + *arrays: ct.ListType, +) -> ct.ListType: + return arrays[0].type + + +class ConcatWs(Function): + """ + concat_ws(separator, [str | array(str)]+) - Returns the concatenation of the + strings separated by separator. + """ + + +@ConcatWs.register # type: ignore +def infer_type( + sep: ct.StringType, + *strings: ct.StringType, +) -> ct.ColumnType: + return ct.StringType() + + +class Contains(Function): + """ + contains(left, right) - Returns a boolean. The value is True if right is found inside left. + Returns NULL if either input expression is NULL. Otherwise, returns False. Both left or + right must be of STRING or BINARY type. + """ + + +@Contains.register # type: ignore +def infer_type(arg1: ct.StringType, arg2: ct.StringType) -> ct.ColumnType: + return ct.BooleanType() + + +@Contains.register # type: ignore +def infer_type( + arg1: ct.BinaryType, + arg2: ct.BinaryType, +) -> ct.ColumnType: # pragma: no cover + return ct.BooleanType() + + +class Conv(Function): + """ + conv(expr, from_base, to_base) - Convert the number expr from from_base to to_base. + """ + + +@Conv.register # type: ignore +def infer_type( + arg1: ct.NumberType, + arg2: ct.IntegerType, + arg3: ct.IntegerType, +) -> ct.ColumnType: + return ct.StringType() + + +@Conv.register # type: ignore +def infer_type( + arg1: ct.StringType, + arg2: ct.IntegerType, + arg3: ct.IntegerType, +) -> ct.ColumnType: + return ct.StringType() + + +class ConvertTimezone(Function): + """ + convert_timezone(from_tz, to_tz, timestamp) - Convert timestamp from from_tz to to_tz. + Spark 3.4+ + """ + + +@ConvertTimezone.register # type: ignore +def infer_type( + arg1: ct.StringType, + arg2: ct.StringType, + arg3: ct.TimestampType, +) -> ct.ColumnType: + return ct.TimestampType() + + +class Corr(Function): + """ + corr(expr1, expr2) - Compute the correlation of expr1 and expr2. + """ + + +@Corr.register # type: ignore +def infer_type( + arg1: ct.NumberType, + arg2: ct.NumberType, +) -> ct.ColumnType: + return ct.FloatType() + + +class Cos(Function): + """ + cos(expr) - Compute the cosine of expr. + """ + + +@Cos.register # type: ignore +def infer_type(arg: ct.NumberType) -> ct.ColumnType: + return ct.FloatType() + + +class Cosh(Function): + """ + cosh(expr) - Compute the hyperbolic cosine of expr. + """ + + +@Cosh.register # type: ignore +def infer_type(arg: ct.NumberType) -> ct.ColumnType: + return ct.FloatType() + + +class Cot(Function): + """ + cot(expr) - Compute the cotangent of expr. + """ + + +@Cot.register # type: ignore +def infer_type(arg: ct.NumberType) -> ct.ColumnType: + return ct.FloatType() + + +class Count(Function): + """ + Counts the number of non-null values in the input column or expression. + """ + + is_aggregation = True + + +@Count.register # type: ignore +def infer_type( + *args: ct.ColumnType, +) -> ct.BigIntType: + return ct.BigIntType() + + +class CountIf(Function): + """ + count_if(expr) - Returns the number of true values in expr. + """ + + +@CountIf.register # type: ignore +def infer_type(arg: ct.BooleanType) -> ct.IntegerType: + return ct.IntegerType() # pragma: no cover + + +class CountMinSketch(Function): + """ + count_min_sketch(col, eps, confidence, seed) - Creates a Count-Min sketch of col. + """ + + +@CountMinSketch.register # type: ignore +def infer_type( + arg1: ct.ColumnType, + arg2: ct.FloatType, + arg3: ct.FloatType, + arg4: ct.IntegerType, +) -> ct.ColumnType: + return ct.BinaryType() + + +class CovarPop(Function): + """ + covar_pop(expr1, expr2) - Returns the population covariance of expr1 and expr2. + """ + + +@CovarPop.register # type: ignore +def infer_type( + arg1: ct.NumberType, + arg2: ct.NumberType, +) -> ct.ColumnType: + return ct.FloatType() + + +class CovarSamp(Function): + """ + covar_samp(expr1, expr2) - Returns the sample covariance of expr1 and expr2. + """ + + +@CovarSamp.register # type: ignore +def infer_type(arg1: ct.NumberType, arg2: ct.NumberType) -> ct.ColumnType: + return ct.FloatType() + + +class Crc32(Function): + """ + crc32(expr) - Computes a cyclic redundancy check value and returns the result as a bigint. + """ + + +@Crc32.register # type: ignore +def infer_type(arg: ct.StringType) -> ct.ColumnType: + return ct.BigIntType() + + +class Csc(Function): + """ + csc(expr) - Computes the cosecant of expr. + """ + + +@Csc.register # type: ignore +def infer_type(arg: ct.NumberType) -> ct.ColumnType: + return ct.FloatType() + + +class CumeDist(Function): + """ + cume_dist() - Computes the cumulative distribution of a value within a group of values. + """ + + +@CumeDist.register # type: ignore +def infer_type() -> ct.ColumnType: + return ct.FloatType() + + +class Curdate(Function): + """ + curdate() - Returns the current date. + """ + + +@Curdate.register # type: ignore +def infer_type() -> ct.ColumnType: + return ct.DateType() + + +class CurrentCatalog(Function): + """ + current_catalog() - Returns the current catalog. + """ + + +@CurrentCatalog.register # type: ignore +def infer_type() -> ct.ColumnType: + return ct.StringType() + + +class CurrentDatabase(Function): + """ + current_database() - Returns the current database. + """ + + +@CurrentDatabase.register # type: ignore +def infer_type() -> ct.ColumnType: + return ct.StringType() + + +class CurrentDate(Function): + """ + Returns the current date. + """ + + +@CurrentDate.register # type: ignore +def infer_type() -> ct.DateType: + return ct.DateType() + + +class CurrentSchema(Function): + """ + current_schema() - Returns the current schema. + """ + + +@CurrentSchema.register # type: ignore +def infer_type() -> ct.ColumnType: + return ct.StringType() + + +class CurrentTime(Function): + """ + Returns the current time. + """ + + +@CurrentTime.register # type: ignore +def infer_type() -> ct.TimeType: + return ct.TimeType() + + +class CurrentTimestamp(Function): + """ + Returns the current timestamp. + """ + + +@CurrentTimestamp.register # type: ignore +def infer_type() -> ct.TimestampType: + return ct.TimestampType() + + +class CurrentTimezone(Function): + """ + current_timezone() - Returns the current timezone. + """ + + +@CurrentTimezone.register # type: ignore +def infer_type() -> ct.ColumnType: + return ct.StringType() + + +class CurrentUser(Function): + """ + current_user() - Returns the current user. + """ + + +@CurrentUser.register # type: ignore +def infer_type() -> ct.ColumnType: + return ct.StringType() + + +class Date(Function): + """ + date(expr) - Converts expr to date. + """ + + +@Date.register # type: ignore +def infer_type(arg: Union[ct.StringType, ct.TimestampType]) -> ct.ColumnType: + return ct.DateType() + + +class DateAdd(Function): + """ + Adds a specified number of days to a date. + """ + + +@DateAdd.register # type: ignore +def infer_type( + start_date: ct.DateType, + days: ct.IntegerBase, +) -> ct.DateType: + return ct.DateType() + + +@DateAdd.register # type: ignore +def infer_type( + start_date: ct.StringType, + days: ct.IntegerBase, +) -> ct.DateType: + return ct.DateType() + + +class Datediff(Function): + """ + Computes the difference in days between two dates. + """ + + +@Datediff.register # type: ignore +def infer_type( + start_date: ct.DateType, + end_date: ct.DateType, +) -> ct.IntegerType: + return ct.IntegerType() + + +@Datediff.register # type: ignore +def infer_type( + start_date: ct.StringType, + end_date: ct.StringType, +) -> ct.IntegerType: + return ct.IntegerType() + + +@Datediff.register # type: ignore +def infer_type( + start_date: ct.TimestampType, + end_date: ct.TimestampType, +) -> ct.IntegerType: + return ct.IntegerType() + + +@Datediff.register # type: ignore +def infer_type( + start_date: ct.IntegerType, + end_date: ct.IntegerType, +) -> ct.IntegerType: + return ct.IntegerType() # pragma: no cover + + +class DateFromUnixDate(Function): + """ + date_from_unix_date(expr) - Converts the number of days from epoch (1970-01-01) to a date. + """ + + +@DateFromUnixDate.register # type: ignore +def infer_type(arg: ct.IntegerType) -> ct.ColumnType: + return ct.DateType() + + +class DateFormat(Function): + """ + date_format(timestamp, fmt) - Converts timestamp to a value of string + in the format specified by the date format fmt. + """ + + +@DateFormat.register # type: ignore +def infer_type( + timestamp: ct.TimestampType, + fmt: ct.StringType, +) -> ct.StringType: + return ct.StringType() + + +class DatePart(Function): + """ + date_part(field, source) - Extracts a part of the date or time given. + """ + + +@DatePart.register # type: ignore +def infer_type( + arg1: ct.StringType, + arg2: Union[ct.DateType, ct.TimestampType], +) -> ct.ColumnType: + # The output can be integer, float, or string depending on the part extracted. + # Here we assume the output is an integer for simplicity. Adjust as needed. + return ct.IntegerType() + + +class DateSub(Function): + """ + Subtracts a specified number of days from a date. + """ + + +@DateSub.register # type: ignore +def infer_type( + start_date: ct.DateType, + days: ct.IntegerBase, +) -> ct.DateType: + return ct.DateType() + + +@DateSub.register # type: ignore +def infer_type( + start_date: ct.StringType, + days: ct.IntegerBase, +) -> ct.DateType: + return ct.DateType() + + +class Day(Function): + """ + Returns the day of the month for a specified date. + """ + + +@Day.register # type: ignore +def infer_type( + arg: Union[ct.StringType, ct.DateType, ct.TimestampType], +) -> ct.IntegerType: # type: ignore + return ct.IntegerType() + + +class Dayofmonth(Function): + """ + dayofmonth(date) - Extracts the day of the month of a given date. + """ + + +@Dayofmonth.register # type: ignore +def infer_type( + arg: Union[ct.DateType, ct.StringType], +) -> ct.ColumnType: + return ct.IntegerType() + + +class Dayofweek(Function): + """ + dayofweek(date) - Extracts the day of the week of a given date. + """ + + +@Dayofweek.register # type: ignore +def infer_type( + arg: Union[ct.DateType, ct.StringType], +) -> ct.ColumnType: + return ct.IntegerType() + + +class Dayofyear(Function): + """ + dayofyear(date) - Extracts the day of the year of a given date. + """ + + +@Dayofyear.register # type: ignore +def infer_type( + arg: Union[ct.DateType, ct.StringType], +) -> ct.ColumnType: + return ct.IntegerType() + + +class Decimal(Function): + """ + decimal(expr, precision, scale) - Converts expr to a decimal number. + """ + + +@Decimal.register # type: ignore +def infer_type( + arg1: Union[ct.IntegerType, ct.FloatType, ct.StringType], +) -> ct.ColumnType: + return ct.DecimalType(8, 6) + + +class Decode(Function): + """ + decode(bin, charset) - Decodes the first argument using the second argument + character set. + + TODO: decode(expr, search, result [, search, result ] ... [, default]) - Compares + expr to each search value in order. If expr is equal to a search value, decode + returns the corresponding result. If no match is found, then it returns default. + If default is omitted, it returns null. + """ + + +@Decode.register # type: ignore +def infer_type(arg1: ct.BinaryType, arg2: ct.StringType) -> ct.ColumnType: + return ct.StringType() + + +class Degrees(Function): + """ + degrees(expr) - Converts radians to degrees. + """ + + +@Degrees.register # type: ignore +def infer_type(arg: ct.NumberType) -> ct.ColumnType: + return ct.FloatType() + + +class DenseRank(Function): # pragma: no cover + """ + TODO + dense_rank() - Computes the dense rank of a value in a group of values. + """ + + +class Div(Function): + """ + TODO + expr1 div expr2 - Divide expr1 by expr2. It returns NULL if an operand is NULL or + expr2 is 0. The result is casted to long. + """ + + +class Double(Function): + """ + double(expr) - Converts expr to a double precision floating-point number. + """ + + +@Double.register # type: ignore +def infer_type( + arg: Union[ct.IntegerType, ct.FloatType, ct.StringType], +) -> ct.ColumnType: + return ct.DoubleType() + + +class E(Function): # pylint: disable=invalid-name + """ + e() - Returns the mathematical constant e. + """ + + +@E.register # type: ignore +def infer_type() -> ct.ColumnType: + return ct.FloatType() + + +class ElementAt(Function): + """ + element_at(array, index) - Returns element of array at given (1-based) index + element_at(map, key) - Returns value for given key. + """ + + +@ElementAt.register +def infer_type( + array: ct.ListType, + _: ct.NumberType, +) -> ct.ColumnType: + return array.type.element.type + + +@ElementAt.register +def infer_type( + map_arg: ct.MapType, + _: ct.NumberType, +) -> ct.ColumnType: + return map_arg.type.value.type + + +class Elt(Function): + """ + elt(n, input1, input2, ...) - Returns the n-th input, e.g., returns input2 when n is 2. + """ + + +@Elt.register # type: ignore +def infer_type(arg1: ct.IntegerType, *args: ct.ColumnType) -> ct.ColumnType: + return args[0].type + + +class Encode(Function): + """ + encode(str, charset) - Encodes str into the provided charset. + """ + + +@Encode.register # type: ignore +def infer_type(arg1: ct.StringType, arg2: ct.StringType) -> ct.ColumnType: + return ct.StringType() + + +class Endswith(Function): + """ + endswith(str, substr) - Returns true if str ends with substr. + """ + + +@Endswith.register # type: ignore +def infer_type(arg1: ct.StringType, arg2: ct.StringType) -> ct.ColumnType: + return ct.BooleanType() + + +class EqualNull(Function): + """ + equal_null(expr1, expr2) - Returns true if expr1 and expr2 are equal or both are null. + """ + + +@EqualNull.register # type: ignore +def infer_type(arg1: ct.ColumnType, arg2: ct.ColumnType) -> ct.ColumnType: + return ct.BooleanType() + + +class Every(Function): + """ + every(expr) - Returns true if all values are true. + """ + + is_aggregation = True + + +@Every.register # type: ignore +def infer_type(arg: ct.BooleanType) -> ct.ColumnType: + return ct.BooleanType() + + +class Exists(Function): + """ + exists(expr, pred) - Tests whether a predicate holds for one or more + elements in the array. + """ + + @staticmethod + def compile_lambda(*args): + """ + Compiles the lambda function used by the `filter` Spark function so that + the lambda's expression can be evaluated to determine the result's type. + """ + from datajunction_server.sql.parsing import ( # pylint: disable=import-outside-toplevel + ast, + ) + + expr, func = args + if len(func.identifiers) != 1: + raise DJParseException( # pragma: no cover + message="The function `exists` takes a lambda function that takes at " + "most one argument.", + ) + lambda_arg_col = [ + col + for col in func.expr.find_all(ast.Column) + if col.alias_or_name.name == func.identifiers[0].name + ][0] + lambda_arg_col.add_type(expr.type.element.type) + + +@Exists.register # type: ignore +def infer_type( + expr: ct.ListType, + pred: ct.BooleanType, +) -> ct.ColumnType: + return ct.BooleanType() + + +class Exp(Function): + """ + Returns e to the power of expr. + """ + + +@Exp.register # type: ignore +def infer_type( + args: ct.ColumnType, +) -> ct.DoubleType: + return ct.DoubleType() + + +class Explode(Function): + """ + explode(expr) - Returns a new row for each element in the given array or map. + """ + + +@Explode.register # type: ignore +def infer_type(arg: ct.ListType) -> ct.ColumnType: + return arg.type.element.type # pragma: no cover + + +@Explode.register # type: ignore +def infer_type(arg: ct.MapType) -> ct.ColumnType: + return arg.type.value.type # pragma: no cover + + +class ExplodeOuter(Function): + """ + explode_outer(expr) - Similar to explode, but returns null if the array/map is null or empty. + """ + + +@ExplodeOuter.register # type: ignore +def infer_type(arg: ct.ListType) -> ct.ColumnType: + return arg.type.element.type + + +@ExplodeOuter.register # type: ignore +def infer_type(arg: ct.MapType) -> ct.ColumnType: + return arg.type.value.type + + +class Expm1(Function): + """ + expm1(expr) - Calculates e^x - 1. + """ + + +@Expm1.register # type: ignore +def infer_type(arg: ct.NumberType) -> ct.ColumnType: + return ct.FloatType() + + +class Extract(Function): + """ + Returns a specified component of a timestamp, such as year, month or day. + """ + + @staticmethod + def infer_type( # type: ignore + field: "Expression", + source: "Expression", + ) -> Union[ct.DecimalType, ct.IntegerType]: + if str(field.name) == "SECOND": # type: ignore + return ct.DecimalType(8, 6) + return ct.IntegerType() + + +class Factorial(Function): + """ + factorial(expr) - Returns the factorial of the number. + """ + + +@Factorial.register # type: ignore +def infer_type(arg: ct.IntegerType) -> ct.ColumnType: + return ct.IntegerType() + + +class Filter(Function): + """ + Filter an array. + """ + + @staticmethod + def compile_lambda(*args): + """ + Compiles the lambda function used by the `filter` Spark function so that + the lambda's expression can be evaluated to determine the result's type. + """ + from datajunction_server.sql.parsing import ( # pylint: disable=import-outside-toplevel + ast, + ) + + expr, func = args + if len(func.identifiers) > 2: + raise DJParseException( + message="The function `filter` takes a lambda function that takes at " + "most two arguments.", + ) + for col in func.expr.find_all(ast.Column): + if ( # pragma: no cover + col.alias_or_name.namespace + and col.alias_or_name.namespace.name + and func.identifiers[0].name == col.alias_or_name.namespace.name + ) or func.identifiers[0].name == col.alias_or_name.name: + col.add_type(expr.type.element.type) + if ( + len(func.identifiers) == 2 + and col.alias_or_name.name == func.identifiers[1].name + ): + col.add_type(ct.LongType()) + + +@Filter.register # type: ignore +def infer_type( + arg: Union[ct.ListType, ct.PrimitiveType], + func: ct.PrimitiveType, +) -> ct.ListType: + return arg.type # type: ignore + + +class FindInSet(Function): + """ + find_in_set(str, str_list) - Returns the index of the first occurrence of str in str_list. + """ + + +@FindInSet.register # type: ignore +def infer_type(arg1: ct.StringType, arg2: ct.StringType) -> ct.ColumnType: + return ct.IntegerType() + + +class First(Function): + """ + Returns the first value of expr for a group of rows. If isIgnoreNull is + true, returns only non-null values. + """ + + is_aggregation = True + + +@First.register +def infer_type( + arg: ct.ColumnType, +) -> ct.ColumnType: + return arg.type + + +@First.register +def infer_type( + arg: ct.ColumnType, + is_ignore_null: ct.BooleanType, +) -> ct.ColumnType: + return arg.type + + +class FirstValue(Function): + """ + Returns the first value of expr for a group of rows. If isIgnoreNull is + true, returns only non-null values. + """ + + is_aggregation = True + + +@FirstValue.register +def infer_type( + arg: ct.ColumnType, +) -> ct.ColumnType: + return arg.type + + +@FirstValue.register +def infer_type( + arg: ct.ColumnType, + is_ignore_null: ct.BooleanType, +) -> ct.ColumnType: + return arg.type + + +class Flatten(Function): + """ + Flatten an array. + """ + + +@Flatten.register # type: ignore +def infer_type( + array: ct.ListType, +) -> ct.ListType: + return array.type.element.type # type: ignore + + +class Float(Function): + """ + float(expr) - Casts the value expr to the target data type float. + """ + + +@Float.register # type: ignore +def infer_type(arg: Union[ct.NumberType, ct.StringType]) -> ct.FloatType: + return ct.FloatType() + + +class Floor(Function): + """ + Returns the largest integer less than or equal to a specified number. + """ + + +@Floor.register # type: ignore +def infer_type( + args: ct.DecimalType, +) -> ct.DecimalType: + return ct.DecimalType(args.type.precision - args.type.scale + 1, 0) + + +@Floor.register # type: ignore +def infer_type( + args: ct.NumberType, +) -> ct.BigIntType: + return ct.BigIntType() + + +@Floor.register # type: ignore +def infer_type( + args: ct.NumberType, + _target_scale: ct.IntegerType, +) -> ct.DecimalType: + target_scale = _target_scale.value + if isinstance(args.type, ct.DecimalType): # pylint: disable=R1705 + precision = max(args.type.precision - args.type.scale + 1, -target_scale + 1) + scale = min(args.type.scale, max(0, target_scale)) + return ct.DecimalType(precision, scale) + if args.type == ct.TinyIntType(): + precision = max(3, -target_scale + 1) + return ct.DecimalType(precision, 0) + if args.type == ct.SmallIntType(): + precision = max(5, -target_scale + 1) + return ct.DecimalType(precision, 0) + if args.type == ct.IntegerType(): + precision = max(10, -target_scale + 1) + return ct.DecimalType(precision, 0) + if args.type == ct.BigIntType(): + precision = max(20, -target_scale + 1) + return ct.DecimalType(precision, 0) + if args.type == ct.FloatType(): + precision = max(14, -target_scale + 1) + scale = min(7, max(0, target_scale)) + return ct.DecimalType(precision, scale) + if args.type == ct.DoubleType(): + precision = max(30, -target_scale + 1) + scale = min(15, max(0, target_scale)) + return ct.DecimalType(precision, scale) + + raise DJParseException( + f"Unhandled numeric type in Floor `{args.type}`", + ) # pragma: no cover + + +class Forall(Function): + """ + forall(expr, predicate) - Returns true if a given predicate holds for all elements of an array. + """ + + @staticmethod + def compile_lambda(*args): + """ + Compiles the lambda function used by the `filter` Spark function so that + the lambda's expression can be evaluated to determine the result's type. + """ + from datajunction_server.sql.parsing import ( # pylint: disable=import-outside-toplevel + ast, + ) + + expr, func = args + if len(func.identifiers) != 1: + raise DJParseException( # pragma: no cover + message="The function `forall` takes a lambda function that takes at " + "most one argument.", + ) + lambda_arg_col = [ + col + for col in func.expr.find_all(ast.Column) + if col.alias_or_name.name == func.identifiers[0].name + ][0] + lambda_arg_col.add_type(expr.type.element.type) + + +@Forall.register # type: ignore +def infer_type(arg1: ct.ListType, arg2: ct.BooleanType) -> ct.ColumnType: + return ct.BooleanType() + + +class FormatNumber(Function): + """ + format_number(x, d) - Formats the number x to a format like '#,###,###.##', + rounded to d decimal places. + """ + + +@FormatNumber.register # type: ignore +def infer_type(arg1: ct.FloatType, arg2: ct.IntegerType) -> ct.StringType: + return ct.StringType() + + +@FormatNumber.register # type: ignore +def infer_type(arg1: ct.FloatType, arg2: ct.StringType) -> ct.StringType: + return ct.StringType() + + +class FormatString(Function): + """ + format_string(format, ...) - Formats the arguments in printf-style. + """ + + +@FormatString.register # type: ignore +def infer_type(arg1: ct.StringType, *args: ct.PrimitiveType) -> ct.StringType: + return ct.StringType() + + +class FromCsv(Function): + """ + from_csv(csvStr, schema, options) - Parses a CSV string and returns a struct. + """ + + +@FromCsv.register # type: ignore +def infer_type( + arg1: ct.StringType, + schema: ct.StringType, + arg3: Optional[ct.MapType] = None, +) -> ct.ColumnType: + # TODO: Handle options? # pylint: disable=fixme + # pylint: disable=import-outside-toplevel + from datajunction_server.sql.parsing.backends.antlr4 import ( + parse_rule, # pragma: no cover + ) + + return ct.StructType( + *parse_rule(schema.value, "complexColTypeList") + ) # pragma: no cover + + +class FromJson(Function): # pragma: no cover + """ + Converts a JSON string to a struct or map. + """ + + +@FromJson.register # type: ignore +def infer_type( # pragma: no cover + json: ct.StringType, + schema: ct.StringType, + options: Optional[Function] = None, +) -> ct.StructType: + # TODO: Handle options? # pylint: disable=fixme + # pylint: disable=import-outside-toplevel + from datajunction_server.sql.parsing.backends.antlr4 import ( + parse_rule, # pragma: no cover + ) + + return ct.StructType( + *parse_rule(schema.value, "complexColTypeList") + ) # pragma: no cover + + +class FromUnixtime(Function): + """ + from_unixtime(unix_time, format) - Converts the number of seconds from the Unix + epoch to a string representing the timestamp. + """ + + +@FromUnixtime.register # type: ignore +def infer_type(arg1: ct.IntegerType, arg2: ct.StringType) -> ct.ColumnType: + return ct.StringType() + + +class FromUtcTimestamp(Function): + """ + from_utc_timestamp(timestamp, timezone) - Renders that time as a timestamp + in the given time zone. + """ + + +@FromUtcTimestamp.register # type: ignore +def infer_type(arg1: ct.TimestampType, arg2: ct.StringType) -> ct.ColumnType: + return ct.TimestampType() + + +@FromUtcTimestamp.register # type: ignore +def infer_type(arg1: ct.StringType, arg2: ct.StringType) -> ct.ColumnType: + return ct.TimestampType() + + +class Get(Function): + """ + get(expr, index) - Retrieves an element from an array at the specified + index or retrieves a value from a map for the given key. + """ + + +@Get.register # type: ignore +def infer_type(arg1: ct.ListType, arg2: ct.IntegerType) -> ct.ColumnType: + return arg1.type.element.type + + +class GetJsonObject(Function): + """ + get_json_object(jsonString, path) - Extracts a JSON object from a JSON + string based on the JSON path specified. + """ + + +@GetJsonObject.register # type: ignore +def infer_type(arg1: ct.StringType, arg2: ct.StringType) -> ct.ColumnType: + return ct.StringType() + + +class GetBit(Function): + """ + getbit(expr, pos) - Returns the value of the bit (0 or 1) at the specified position. + """ + + +@GetBit.register # type: ignore +def infer_type(arg1: ct.IntegerType, arg2: ct.IntegerType) -> ct.ColumnType: + return ct.IntegerType() + + +class Greatest(Function): + """ + greatest(expr, ...) - Returns the greatest value of all parameters, skipping null values. + """ + + +@Greatest.register # type: ignore +def infer_type( + *values: ct.NumberType, +) -> ct.ColumnType: + return values[0].type + + +class Grouping(Function): + """ + grouping(col) - Returns 1 if the specified column is aggregated, and 0 otherwise. + """ + + +@Grouping.register # type: ignore +def infer_type(arg: ct.ColumnType) -> ct.ColumnType: + return ct.IntegerType() + + +class GroupingId(Function): + """ + grouping_id(cols) - Returns a bit vector with a bit for each grouping column. + """ + + +@GroupingId.register # type: ignore +def infer_type(*args: ct.ColumnType) -> ct.ColumnType: + return ct.BigIntType() + + +class Hash(Function): + """ + hash(args) - Returns a hash value of the arguments. + """ + + +@Hash.register # type: ignore +def infer_type(*args: ct.ColumnType) -> ct.ColumnType: + return ct.IntegerType() + + +class Hex(Function): + """ + hex(expr) - Converts a number or a string to a hexadecimal string. + """ + + +@Hex.register # type: ignore +def infer_type(arg: Union[ct.IntegerType, ct.StringType]) -> ct.ColumnType: + return ct.StringType() + + +class HistogramNumeric(Function): + """ + histogram_numeric(col, numBins) - Generates a histogram using a series of buckets + defined by equally spaced width intervals. + """ + + +@HistogramNumeric.register # type: ignore +def infer_type(arg1: ct.ColumnType, arg2: ct.IntegerType) -> ct.ColumnType: + # assuming that there's a StructType for the bin and frequency + from datajunction_server.sql.parsing import ( # pylint: disable=import-outside-toplevel + ast, + ) + + return ct.ListType( + element_type=ct.StructType( + ct.NestedField(ast.Name("x"), ct.FloatType()), + ct.NestedField(ast.Name("y"), ct.FloatType()), + ), + ) + + +class Hour(Function): + """ + hour(timestamp) - Extracts the hour from a timestamp. + """ + + +@Hour.register # type: ignore +def infer_type( + arg: Union[ct.TimestampType, ct.StringType], +) -> ct.ColumnType: + return ct.IntegerType() + + +class Hypot(Function): + """ + hypot(a, b) - Returns sqrt(a^2 + b^2) without intermediate overflow or underflow. + """ + + +@Hypot.register # type: ignore +def infer_type(arg1: ct.NumberType, arg2: ct.NumberType) -> ct.ColumnType: + return ct.FloatType() + + +class If(Function): + """ + If statement + + if(condition, result, else_result): if condition evaluates to true, + then returns result; otherwise returns else_result. + """ + + +@If.register # type: ignore +def infer_type( + cond: ct.BooleanType, + then: ct.ColumnType, + else_: ct.ColumnType, +) -> ct.ColumnType: + if not then.type.is_compatible(else_.type): + raise DJInvalidInputException( + message="The then result and else result must match in type! " + f"Got {then.type} and {else_.type}", + ) + + return then.type + + +class IfNull(Function): + """ + Returns the second expression if the first is null, else returns the first expression. + """ + + +@IfNull.register +def infer_type(*args: ct.ColumnType) -> ct.ColumnType: + return args[0].type if args[1].type == ct.NullType() else args[1].type + + +class ILike(Function): + """ + ilike(str, pattern) - Performs case-insensitive LIKE match. + """ + + +@ILike.register # type: ignore +def infer_type(arg1: ct.StringType, arg2: ct.StringType) -> ct.ColumnType: + return ct.BooleanType() + + +class InitCap(Function): + """ + initcap(str) - Converts the first letter of each word in the string to uppercase + and the rest to lowercase. + """ + + +@InitCap.register # type: ignore +def infer_type(arg: ct.StringType) -> ct.ColumnType: + return ct.StringType() + + +class Inline(Function): + """ + inline(array_of_struct) - Explodes an array of structs into a table. + """ + + +@Inline.register # type: ignore +def infer_type(arg: ct.ListType) -> ct.ColumnType: + # The output type is the type of the struct's fields + return arg.type.element.type + + +class InlineOuter(Function): + """ + inline_outer(array_of_struct) - Similar to inline, but includes nulls if the size + of the array is less than the size of the outer array. + """ + + +@InlineOuter.register # type: ignore +def infer_type(arg: ct.ListType) -> ct.ColumnType: + # The output type is the type of the struct's fields + return arg.type.element.type + + +class InputFileBlockLength(Function): + """ + input_file_block_length() - Returns the length of the current block being read from HDFS. + """ + + +@InputFileBlockLength.register # type: ignore +def infer_type() -> ct.ColumnType: + return ct.LongType() + + +class InputFileBlockStart(Function): + """ + input_file_block_start() - Returns the start offset of the current block being read from HDFS. + """ + + +@InputFileBlockStart.register # type: ignore +def infer_type() -> ct.ColumnType: + return ct.LongType() + + +class InputFileName(Function): + """ + input_file_name() - Returns the name of the current file being read from HDFS. + """ + + +@InputFileName.register # type: ignore +def infer_type() -> ct.ColumnType: + return ct.StringType() + + +class Instr(Function): + """ + instr(str, substring) - Returns the position of the first occurrence of substring in string. + """ + + +@Instr.register # type: ignore +def infer_type(arg1: ct.StringType, arg2: ct.StringType) -> ct.ColumnType: + return ct.IntegerType() + + +class Int(Function): + """ + int(expr) - Casts the value expr to the target data type int. + """ + + +@Int.register # type: ignore +def infer_type( + arg: ct.ColumnType, +) -> ct.ColumnType: + return ct.IntegerType() + + +class Isnan(Function): + """ + isnan(expr) - Tests if a value is NaN. + """ + + +@Isnan.register # type: ignore +def infer_type(arg: ct.NumberType) -> ct.ColumnType: + return ct.BooleanType() + + +class Isnotnull(Function): + """ + isnotnull(expr) - Tests if a value is not null. + """ + + +@Isnotnull.register # type: ignore +def infer_type(arg: ct.ColumnType) -> ct.ColumnType: + return ct.BooleanType() + + +class Isnull(Function): + """ + isnull(expr) - Tests if a value is null. + """ + + +@Isnull.register # type: ignore +def infer_type(arg: ct.ColumnType) -> ct.ColumnType: + return ct.BooleanType() + + +class JsonArrayLength(Function): + """ + json_array_length(jsonArray) - Returns the length of the JSON array. + """ + + +@JsonArrayLength.register # type: ignore +def infer_type(arg: ct.StringType) -> ct.ColumnType: + return ct.IntegerType() + + +class JsonObjectKeys(Function): + """ + json_object_keys(jsonObject) - Returns all the keys of the JSON object. + """ + + +@JsonObjectKeys.register # type: ignore +def infer_type(arg: ct.StringType) -> ct.ColumnType: + return ct.ListType(element_type=ct.StringType()) + + +class JsonTuple(Function): + """ + json_tuple(json_str, path1, path2, ...) - Extracts multiple values from a JSON object. + """ + + +@JsonTuple.register # type: ignore +def infer_type(json_str: ct.StringType, *paths: ct.StringType) -> ct.ColumnType: + # assuming that there's a TupleType for the extracted values + return ct.ListType(element_type=ct.StringType()) + + +class Kurtosis(Function): + """ + kurtosis(expr) - Returns the kurtosis of the values in a group. + """ + + +@Kurtosis.register # type: ignore +def infer_type(arg: ct.NumberType) -> ct.DoubleType: + return ct.DoubleType() + + +class Lag(Function): + """ + lag(expr[, offset[, default]]) - Returns the value that is `offset` rows + before the current row in a window partition. + """ + + +@Lag.register # type: ignore +def infer_type( + arg: ct.ColumnType, + offset: Optional[ct.IntegerType] = None, + default: Optional[ct.ColumnType] = None, +) -> ct.ColumnType: + # The output type is the same as the input expression's type + return arg.type + + +class Last(Function): + """ + last(expr[, ignoreNulls]) - Returns the last value of `expr` for a group of rows. + """ + + is_aggregation = True + + +@Last.register # type: ignore +def infer_type( + arg: ct.ColumnType, + ignore_nulls: Optional[ct.BooleanType] = None, +) -> ct.ColumnType: + # The output type is the same as the input expression's type + return arg.type + + +class LastDay(Function): + """ + last_day(date) - Returns the last day of the month which the date belongs to. + """ + + +@LastDay.register # type: ignore +def infer_type(arg: Union[ct.DateType, ct.StringType]) -> ct.ColumnType: + return ct.DateType() + + +class LastValue(Function): + """ + last_value(expr[, ignoreNulls]) - Returns the last value in an ordered set of values. + """ + + is_aggregation = True + + +@LastValue.register # type: ignore +def infer_type( + arg: ct.ColumnType, + ignore_nulls: Optional[ct.BooleanType] = None, +) -> ct.ColumnType: + # The output type is the same as the input expression's type + return arg.type + + +class Lcase(Function): + """ + lcase(str) - Converts the string to lowercase. + """ + + +@Lcase.register # type: ignore +def infer_type(arg: ct.StringType) -> ct.ColumnType: + return ct.StringType() + + +class Lead(Function): + """ + lead(expr[, offset[, default]]) - Returns the value that is `offset` + rows after the current row in a window partition. + """ + + +@Lead.register # type: ignore +def infer_type( + arg: ct.ColumnType, + offset: Optional[ct.IntegerType] = None, + default: Optional[ct.ColumnType] = None, +) -> ct.ColumnType: + # The output type is the same as the input expression's type + return arg.type + + +class Least(Function): + """ + least(expr1, expr2, ...) - Returns the smallest value of the list of values. + """ + + +@Least.register # type: ignore +def infer_type(*args: ct.ColumnType) -> ct.ColumnType: + # The output type is the same as the input expressions' type + # Assuming all input expressions have the same type + return args[0].type + + +class Left(Function): + """ + left(str, len) - Returns the leftmost `len` characters from the string. + """ + + +@Left.register # type: ignore +def infer_type( + arg1: ct.StringType, + arg2: ct.IntegerType, +) -> ct.StringType: + return ct.StringType() # pragma: no cover # see test_left_func + + +class Len(Function): + """ + len(str) - Returns the length of the string. + """ + + +@Len.register # type: ignore +def infer_type(arg: ct.StringType) -> ct.IntegerType: + return ct.IntegerType() + + +class Length(Function): + """ + Returns the length of a string. + """ + + +@Length.register # type: ignore +def infer_type( + arg: ct.StringType, +) -> ct.IntegerType: + return ct.IntegerType() + + +class Levenshtein(Function): + """ + Returns the Levenshtein distance between two strings. + """ + + +@Levenshtein.register # type: ignore +def infer_type( + string1: ct.StringType, + string2: ct.StringType, +) -> ct.IntegerType: + return ct.IntegerType() + + +class Like(Function): + """ + like(str, pattern) - Performs pattern matching using SQL's LIKE operator. + """ + + +@Like.register # type: ignore +def infer_type(arg1: ct.StringType, arg2: ct.StringType) -> ct.ColumnType: + return ct.BooleanType() + + +class Ln(Function): + """ + Returns the natural logarithm of a number. + """ + + +@Ln.register # type: ignore +def infer_type( + args: ct.ColumnType, +) -> ct.DoubleType: + return ct.DoubleType() + + +class Localtimestamp(Function): + """ + localtimestamp() - Returns the current timestamp at the system's local time zone. + """ + + +@Localtimestamp.register # type: ignore +def infer_type() -> ct.ColumnType: + return ct.TimestampType() + + +class Locate(Function): + """ + locate(substr, str[, pos]) - Returns the position of the first occurrence of substr in str. + """ + + +@Locate.register # type: ignore +def infer_type( + arg1: ct.StringType, + arg2: ct.StringType, + pos: Optional[ct.IntegerType] = None, +) -> ct.ColumnType: + return ct.IntegerType() + + +class Log(Function): + """ + Returns the logarithm of a number with the specified base. + """ + + +@Log.register # type: ignore +def infer_type( + base: ct.ColumnType, + expr: ct.ColumnType, +) -> ct.DoubleType: + return ct.DoubleType() + + +class Log10(Function): + """ + Returns the base-10 logarithm of a number. + """ + + +@Log10.register # type: ignore +def infer_type( + args: ct.ColumnType, +) -> ct.DoubleType: + return ct.DoubleType() + + +class Log1p(Function): + """ + log1p(expr) - Returns the natural logarithm of the given value plus one. + """ + + +@Log1p.register # type: ignore +def infer_type(arg: ct.NumberType) -> ct.DoubleType: + return ct.DoubleType() + + +class Log2(Function): + """ + Returns the base-2 logarithm of a number. + """ + + +@Log2.register # type: ignore +def infer_type( + args: ct.ColumnType, +) -> ct.DoubleType: + return ct.DoubleType() + + +class Lower(Function): + """ + Converts a string to lowercase. + """ + + @staticmethod + def infer_type(arg: "Expression") -> ct.StringType: # type: ignore + return ct.StringType() + + +class Lpad(Function): + """ + lpad(str, len[, pad]) - Left-pads the string with pad to a length of len. + If str is longer than len, the return value is shortened to len characters. + """ + + +@Lpad.register # type: ignore +def infer_type( + arg1: ct.StringType, + arg2: ct.IntegerType, + pad: Optional[ct.StringType] = None, +) -> ct.StringType: + return ct.StringType() + + +class Ltrim(Function): + """ + ltrim(str[, trimStr]) - Trims the spaces from left end of the string. + """ + + +@Ltrim.register # type: ignore +def infer_type( + arg1: ct.StringType, + trim_str: Optional[ct.StringType] = None, +) -> ct.StringType: + return ct.StringType() + + +class MakeDate(Function): + """ + make_date(year, month, day) - Creates a date from the given year, month, and day. + """ + + +@MakeDate.register # type: ignore +def infer_type( + year: ct.IntegerType, + month: ct.IntegerType, + day: ct.IntegerType, +) -> ct.ColumnType: + return ct.DateType() + + +class MakeDtInterval(Function): + """ + make_dt_interval(days, hours, mins, secs) - Returns a day-time interval. + """ + + +@MakeDtInterval.register # type: ignore +def infer_type( + days: ct.IntegerType, + hours: ct.IntegerType, + mins: ct.IntegerType, + secs: ct.IntegerType, +) -> ct.DayTimeIntervalType: + return ct.DayTimeIntervalType() + + +class MakeInterval(Function): + """ + make_interval(years, months) - Returns a year-month interval. + """ + + +@MakeInterval.register # type: ignore +def infer_type( + years: ct.IntegerType, + months: ct.IntegerType, +) -> ct.YearMonthIntervalType: + return ct.YearMonthIntervalType() + + +class MakeTimestamp(Function): + """ + make_timestamp(year, month, day, hour, min, sec) - Returns a timestamp + made from the arguments. + """ + + +@MakeTimestamp.register # type: ignore +def infer_type( # pylint: disable=too-many-arguments + year: ct.IntegerType, + month: ct.IntegerType, + day: ct.IntegerType, + hour: ct.IntegerType, + min_: ct.IntegerType, + sec: ct.IntegerType, +) -> ct.TimestampType: + return ct.TimestampType() + + +class MakeTimestampLtz(Function): + """ + make_timestamp_ltz(year, month, day, hour, min, sec, timezone) + Returns a timestamp with local time zone. + """ + + +@MakeTimestampLtz.register # type: ignore +def infer_type( # pylint: disable=too-many-arguments + year: ct.IntegerType, + month: ct.IntegerType, + day: ct.IntegerType, + hour: ct.IntegerType, + min_: ct.IntegerType, + sec: ct.IntegerType, + timezone: Optional[ct.StringType] = None, +) -> ct.TimestampType: + return ct.TimestampType() + + +class MakeTimestampNtz(Function): + """ + make_timestamp_ntz(year, month, day, hour, min, sec) + Returns a timestamp without time zone. + """ + + +@MakeTimestampNtz.register # type: ignore +def infer_type( # pylint: disable=too-many-arguments + year: ct.IntegerType, + month: ct.IntegerType, + day: ct.IntegerType, + hour: ct.IntegerType, + min_: ct.IntegerType, + sec: ct.IntegerType, +) -> ct.TimestampType: + return ct.TimestampType() + + +class MakeYmInterval(Function): + """ + make_ym_interval(years, months) - Returns a year-month interval. + """ + + +@MakeYmInterval.register # type: ignore +def infer_type( + years: ct.IntegerType, + months: ct.IntegerType, +) -> ct.YearMonthIntervalType: + return ct.YearMonthIntervalType() + + +class Map(Function): + """ + Returns a map of constants + """ + + +@Map.register # type: ignore +def infer_type( + *args: ct.ColumnType, +) -> ct.MapType: + return ct.MapType(key_type=args[0].type, value_type=args[1].type) + + +class MapConcat(Function): + """ + map_concat(map, ...) - Concatenates all the given maps into one. + """ + + +@MapConcat.register # type: ignore +def infer_type(*args: ct.MapType) -> ct.MapType: + return args[0].type + + +class MapContainsKey(Function): + """ + map_contains_key(map, key) - Returns true if the map contains the given key. + """ + + +@MapContainsKey.register # type: ignore +def infer_type(map_: ct.MapType, key: ct.ColumnType) -> ct.BooleanType: + return ct.BooleanType() + + +class MapEntries(Function): + """ + map_entries(map) - Returns an unordered array of all entries in the given map. + """ + + +@MapEntries.register # type: ignore +def infer_type(map_: ct.MapType) -> ct.ColumnType: + return ct.ListType( + element_type=ct.StructType( + ct.NestedField("key", field_type=map_.type.key.type), + ct.NestedField("value", field_type=map_.type.value.type), + ), + ) + + +class MapFilter(Function): + """ + map_filter(map, function) - Returns a map that only includes the entries that match the + given predicate. + """ + + @staticmethod + def compile_lambda(*args): + """ + Compiles the lambda function used by the `map_filter` Spark function so that + the lambda's expression can be evaluated to determine the result's type. + """ + from datajunction_server.sql.parsing import ( # pylint: disable=import-outside-toplevel + ast, + ) + + expr, func = args + if len(func.identifiers) != 2: + raise DJParseException( # pragma: no cover + message="The function `map_filter` takes a lambda function that takes " + "exactly two arguments.", + ) + identifiers = {iden.name: idx for idx, iden in enumerate(func.identifiers)} + lambda_arg_cols = { + identifiers[col.alias_or_name.name]: col + for col in func.expr.find_all(ast.Column) + if col.alias_or_name.name in identifiers + } + lambda_arg_cols[0].add_type(expr.type.key.type) + lambda_arg_cols[1].add_type(expr.type.value.type) + + +@MapFilter.register # type: ignore +def infer_type(map_: ct.MapType, function: ct.BooleanType) -> ct.MapType: + return map_.type + + +class MapFromArrays(Function): + """ + map_from_arrays(keys, values) - Creates a map from two arrays. + """ + + +@MapFromArrays.register # type: ignore +def infer_type(keys: ct.ListType, values: ct.ListType) -> ct.MapType: + return ct.MapType( + key_type=keys.type.element.type, + value_type=values.type.element.type, + ) + + +class MapFromEntries(Function): + """ + map_from_entries(array) - Creates a map from an array of entries. + """ + + +@MapFromEntries.register # type: ignore +def infer_type(array_of_entries: ct.ListType) -> ct.ColumnType: + entry = cast(ct.StructType, array_of_entries.type.element.type) + key, value = entry.fields + return ct.MapType(key_type=key.type, value_type=value.type) + + +class MapKeys(Function): + """ + map_keys(map) - Returns an unordered array containing the keys of the map. + """ + + +@MapKeys.register # type: ignore +def infer_type( + map_: ct.MapType, +) -> ct.ColumnType: + return ct.ListType(element_type=map_.type.key.type) + + +class MapValues(Function): + """ + map_values(map) - Returns an unordered array containing the values of the map. + """ + + +@MapValues.register # type: ignore +def infer_type(map_: ct.MapType) -> ct.ColumnType: + return ct.ListType(element_type=map_.type.value.type) + + +# TODO # pylint: disable=fixme +# class MapZipWith(Function): +# """ +# map_zip_with(map1, map2, function) - Returns a merged map of two given maps by +# applying function to the pair of values with the same key. +# """ +# +# +# @MapZipWith.register # type: ignore +# def infer_type( +# map1: ct.MapType, map2: ct.MapType, function: ct.ColumnType +# ) -> ct.ColumnType: +# return ct.MapType() + + +class Mask(Function): + """ + mask(input[, upperChar, lowerChar, digitChar, otherChar]) - masks the + given string value. The function replaces characters with 'X' or 'x', + and numbers with 'n'. This can be useful for creating copies of tables + with sensitive information removed. + """ + + +@Mask.register # type: ignore +def infer_type( + input_: ct.StringType, + upper: Optional[ct.StringType] = None, + lower: Optional[ct.StringType] = None, + digit: Optional[ct.StringType] = None, + other: Optional[ct.StringType] = None, +) -> ct.StringType: + return ct.StringType() + + +class Max(Function): + """ + Computes the maximum value of the input column or expression. + """ + + is_aggregation = True + + +@Max.register # type: ignore +def infer_type( + arg: ct.NumberType, +) -> ct.NumberType: + return arg.type + + +@Max.register # type: ignore +def infer_type( + arg: ct.StringType, +) -> ct.StringType: + return arg.type + + +class MaxBy(Function): + """ + max_by(val, key) - Returns the value of val corresponding to the maximum value of key. + """ + + +@MaxBy.register # type: ignore +def infer_type(val: ct.ColumnType, key: ct.ColumnType) -> ct.ColumnType: + return val.type + + +class Md5(Function): + """ + md5(expr) - Calculates the MD5 hash of the given value. + """ + + +@Md5.register # type: ignore +def infer_type(arg: ct.ColumnType) -> ct.ColumnType: + return ct.StringType() + + +class Mean(Function): + """ + mean(expr) - Returns the average of the values in the group. + """ + + +@Mean.register # type: ignore +def infer_type(arg: ct.ColumnType) -> ct.ColumnType: + return ct.DoubleType() + + +class Median(Function): + """ + median(expr) - Returns the median of the values in the group. + """ + + +@Median.register # type: ignore +def infer_type(arg: ct.NumberType) -> ct.ColumnType: + return ct.DoubleType() + + +# TODO: fix parsing of: # pylint: disable=fixme +# SELECT median(col) FROM VALUES (INTERVAL '0' MONTH), +# (INTERVAL '10' MONTH) AS tab(col) +# in order to test this +@Median.register # type: ignore +def infer_type(arg: ct.IntervalTypeBase) -> ct.IntervalTypeBase: # pragma: no cover + return arg.type + + +class Min(Function): + """ + Computes the minimum value of the input column or expression. + """ + + is_aggregation = True + + +@Min.register # type: ignore +def infer_type( + arg: ct.NumberType, +) -> ct.NumberType: + return arg.type + + +class MinBy(Function): + """ + min_by(val, key) - Returns the value of val corresponding to the minimum value of key. + """ + + +@MinBy.register # type: ignore +def infer_type(val: ct.ColumnType, key: ct.ColumnType) -> ct.ColumnType: + return val.type + + +class Minute(Function): + """ + minute(timestamp) - Returns the minute component of the string/timestamp + """ + + +@Minute.register # type: ignore +def infer_type(val: Union[ct.StringType, ct.TimestampType]) -> ct.IntegerType: + return ct.IntegerType() + + +class Mod(Function): + """ + mod(expr1, expr2) - Returns the remainder after expr1/expr2. + """ + + +@Mod.register # type: ignore +def infer_type(expr1: ct.NumberType, expr2: ct.NumberType) -> ct.FloatType: + return ct.FloatType() + + +class Mode(Function): + """ + mode(col) - Returns the most frequent value for the values within col. + """ + + +@Mode.register # type: ignore +def infer_type(arg: ct.ColumnType) -> ct.ColumnType: + return arg.type + + +class MonotonicallyIncreasingId(Function): + """ + monotonically_increasing_id() - Returns monotonically increasing 64-bit integers + """ + + +@MonotonicallyIncreasingId.register # type: ignore +def infer_type() -> ct.BigIntType: + return ct.BigIntType() + + +class Month(Function): + """ + Extracts the month of a date or timestamp. + """ + + +@Month.register +def infer_type(arg: Union[ct.StringType, ct.DateTimeBase]) -> ct.BigIntType: + return ct.BigIntType() + + +class MonthsBetween(Function): + """ + months_between(timestamp1, timestamp2[, roundOff]) + """ + + +@MonthsBetween.register +def infer_type( + arg: Union[ct.StringType, ct.TimestampType], + arg2: Union[ct.StringType, ct.TimestampType], + arg3: Optional[ct.BooleanType] = None, +) -> ct.BigIntType: + return ct.FloatType() + + +class NamedStruct(Function): + """ + named_struct(name, val, ...) - Creates a new struct with the given field names and values. + """ + + +@NamedStruct.register # type: ignore +def infer_type(*args: ct.ColumnType) -> ct.ColumnType: + args_iter = iter(args) + nested_fields = [ + ct.NestedField( + name=field_name.value.replace("'", ""), + field_type=field_value.type, + ) + for field_name, field_value in zip(args_iter, args_iter) + ] + return ct.StructType(*nested_fields) + + +class Nanvl(Function): + """ + nanvl(expr1, expr2) - Returns the first argument if it is not NaN, + or the second argument if the first argument is NaN. + """ + + +@Nanvl.register # type: ignore +def infer_type(expr1: ct.NumberType, expr2: ct.NumberType) -> ct.NumberType: + return expr1.type + + +class Negative(Function): + """ + negative(expr) - Returns the negated value of the input expression. + """ + + +@Negative.register # type: ignore +def infer_type(arg: ct.NumberType) -> ct.NumberType: + return arg.type + + +class NextDay(Function): + """ + next_day(start_date, day_of_week) - Returns the first date which is + later than start_date and named as indicated. + """ + + +@NextDay.register # type: ignore +def infer_type( + date: Union[ct.DateType, ct.StringType], + day_of_week: ct.StringType, +) -> ct.ColumnType: + return ct.DateType() + + +class Not(Function): + """ + not(expr) - Returns the logical NOT of the Boolean expression. + """ + + +@Not.register # type: ignore +def infer_type(arg: ct.BooleanType) -> ct.ColumnType: + return ct.BooleanType() # pragma: no cover + + +class Now(Function): + """ + Returns the current timestamp. + """ + + +@Now.register # type: ignore +def infer_type() -> ct.TimestampType: + return ct.TimestampType() + + +class NthValue(Function): + """ + nth_value(input[, offset]) - Returns the value of input at the row + that is the offset-th row from beginning of the window frame + """ + + +@NthValue.register # type: ignore +def infer_type(expr: ct.ColumnType, offset: ct.IntegerType) -> ct.ColumnType: + return expr.type + + +class Ntile(Function): + """ + ntile(n) - Divides the rows for each window partition into n buckets + ranging from 1 to at most n. + """ + + +@Ntile.register # type: ignore +def infer_type(n_buckets: ct.IntegerType) -> ct.ColumnType: + return ct.IntegerType() + + +class Nullif(Function): + """ + nullif(expr1, expr2) - Returns null if expr1 equals expr2, or expr1 otherwise. + """ + + +@Nullif.register # type: ignore +def infer_type(expr1: ct.ColumnType, expr2: ct.ColumnType) -> ct.ColumnType: + return expr1.type + + +class Nvl(Function): + """ + nvl(expr1, expr2) - Returns the first argument if it is not null, or the + second argument if the first argument is null. + """ + + +@Nvl.register # type: ignore +def infer_type(expr1: ct.ColumnType, expr2: ct.ColumnType) -> ct.ColumnType: + return expr1.type + + +class Nvl2(Function): + """ + nvl2(expr1, expr2, expr3) - Returns expr3 if expr1 is null, or expr2 otherwise. + """ + + +@Nvl2.register # type: ignore +def infer_type( + expr1: ct.ColumnType, + expr2: ct.ColumnType, + expr3: ct.ColumnType, +) -> ct.ColumnType: + return expr1.type + + +class OctetLength(Function): + """ + octet_length(expr) - Returns the number of bytes in the input string. + """ + + +@OctetLength.register # type: ignore +def infer_type(expr: ct.StringType) -> ct.ColumnType: + return ct.IntegerType() + + +class Overlay(Function): + """ + overlay(expr1, expr2, start[, length]) - Replaces the substring of expr1 + specified by start (and optionally length) with expr2. + """ + + +@Overlay.register # type: ignore +def infer_type( + input_: ct.StringType, + replace: ct.StringType, + pos: ct.IntegerType, + length: Optional[ct.IntegerType] = None, +) -> ct.ColumnType: + return ct.StringType() + + +class PercentRank(Function): + """ + percent_rank() - Computes the percentage ranking of a value in a group of values. + """ + + is_aggregation = True + + +@PercentRank.register +def infer_type(arg: ct.NumberType) -> ct.DoubleType: + return ct.DoubleType() + + +class Pow(Function): + """ + Raises a base expression to the power of an exponent expression. + """ + + +@Pow.register # type: ignore +def infer_type( + base: ct.ColumnType, + power: ct.ColumnType, +) -> ct.DoubleType: + return ct.DoubleType() + + +class Power(Function): + """ + Raises a base expression to the power of an exponent expression. + """ + + +@Power.register # type: ignore +def infer_type( + base: ct.ColumnType, + power: ct.ColumnType, +) -> ct.DoubleType: + return ct.DoubleType() + + +class Rank(Function): + """ + rank() - Computes the rank of a value in a group of values. The result is + one plus the number of rows preceding or equal to the current row in the + ordering of the partition. The values will produce gaps in the sequence. + """ + + +@Rank.register +def infer_type() -> ct.IntegerType: + return ct.IntegerType() + + +class RegexpLike(Function): + """ + regexp_like(str, regexp) - Returns true if str matches regexp, or false otherwise + """ + + +@RegexpLike.register +def infer_type( # type: ignore + arg1: ct.StringType, + arg2: ct.StringType, +) -> ct.BooleanType: + return ct.BooleanType() + + +class RowNumber(Function): + """ + row_number() - Assigns a unique, sequential number to each row, starting with + one, according to the ordering of rows within the window partition. + """ + + +@RowNumber.register +def infer_type() -> ct.IntegerType: + return ct.IntegerType() + + +class Round(Function): + """ + Rounds a numeric column or expression to the specified number of decimal places. + """ + + +@Round.register # type: ignore +def infer_type( + child: ct.DecimalType, + scale: ct.IntegerBase, +) -> ct.NumberType: + child_type = child.type + integral_least_num_digits = child_type.precision - child_type.scale + 1 + if scale.value < 0: + new_precision = max( + integral_least_num_digits, + -scale.type.value + 1, + ) # pragma: no cover + return ct.DecimalType(new_precision, 0) # pragma: no cover + new_scale = min(child_type.scale, scale.value) + return ct.DecimalType(integral_least_num_digits + new_scale, new_scale) + + +@Round.register +def infer_type( # type: ignore + child: ct.NumberType, + scale: ct.IntegerBase, +) -> ct.NumberType: + if scale.value == 0: + return ct.IntegerType() + return child.type + + +@Round.register +def infer_type( # type: ignore + child: ct.NumberType, +) -> ct.NumberType: + return ct.IntegerType() + + +class Size(Function): + """ + size(expr) - Returns the size of an array or a map. The function returns + null for null input if spark.sql.legacy.sizeOfNull is set to false or + spark.sql.ansi.enabled is set to true. Otherwise, the function returns -1 + for null input. With the default settings, the function returns -1 for null + input. + """ + + +@Size.register # type: ignore +def infer_type( + arg: ct.ListType, +) -> ct.IntegerType: + return ct.IntegerType() + + +@Size.register # type: ignore +def infer_type( + arg: ct.MapType, +) -> ct.IntegerType: + return ct.IntegerType() + + +class Split(Function): + """ + Splits str around occurrences that match regex and returns an + array with a length of at most limit + """ + + +@Split.register +def infer_type( + string: ct.StringType, + regex: ct.StringType, + limit: Optional[ct.IntegerType] = None, +) -> ct.ColumnType: + return ct.ListType(element_type=ct.StringType()) # type: ignore + + +class Sqrt(Function): + """ + Computes the square root of a numeric column or expression. + """ + + +@Sqrt.register +def infer_type(arg: ct.NumberType) -> ct.DoubleType: + return ct.DoubleType() + + +class Stddev(Function): + """ + Computes the sample standard deviation of a numerical column or expression. + """ + + is_aggregation = True + + +@Stddev.register +def infer_type(arg: ct.NumberType) -> ct.DoubleType: + return ct.DoubleType() + + +class StddevPop(Function): + """ + Computes the population standard deviation of the input column or expression. + """ + + is_aggregation = True + + +@StddevPop.register +def infer_type(arg: ct.NumberType) -> ct.DoubleType: + return ct.DoubleType() + + +class StddevSamp(Function): + """ + Computes the sample standard deviation of the input column or expression. + """ + + is_aggregation = True + + +@StddevSamp.register +def infer_type(arg: ct.NumberType) -> ct.DoubleType: + return ct.DoubleType() + + +class Strpos(Function): + """ + strpos(string, substring) -> bigint + Returns the starting position of the first instance of substring in string. Positions + start with 1. If not found, 0 is returned. + strpos(string, substring, instance) -> bigint + Returns the position of the N-th instance of substring in string. When instance is a + negative number the search will start from the end of string. Positions start with 1. + If not found, 0 is returned. + Note: Trino-only + """ + + +@Strpos.register +def infer_type( + string: ct.StringType, + substring: ct.StringType, +) -> ct.IntegerType: + return ct.IntegerType() + + +@Strpos.register +def infer_type( + string: ct.StringType, + substring: ct.StringType, + instance: ct.IntegerType, +) -> ct.IntegerType: + return ct.IntegerType() + + +class Struct(Function): + """ + struct(val1, val2, ...) - Creates a new struct with the given field values. + """ + + +@Struct.register # type: ignore +def infer_type(*args: ct.ColumnType) -> ct.StructType: + return ct.StructType( + *[ + ct.NestedField( + name=arg.alias.name if hasattr(arg, "alias") else f"col{idx}", + field_type=arg.type, + ) + for idx, arg in enumerate(args) + ], + ) + + +class Substring(Function): + """ + Extracts a substring from a string column or expression. + """ + + +@Substring.register +def infer_type( # type: ignore + string: ct.StringType, + pos: ct.IntegerType, +) -> ct.StringType: + return ct.StringType() + + +@Substring.register +def infer_type( # type: ignore + string: ct.StringType, + pos: ct.IntegerType, + length: ct.IntegerType, +) -> ct.StringType: + return ct.StringType() + + +class Sum(Function): + """ + Computes the sum of the input column or expression. + """ + + is_aggregation = True + + +@Sum.register # type: ignore +def infer_type( + arg: ct.IntegerBase, +) -> ct.BigIntType: + return ct.BigIntType() + + +@Sum.register # type: ignore +def infer_type( + arg: ct.DecimalType, +) -> ct.DecimalType: + precision = arg.type.precision + scale = arg.type.scale + return ct.DecimalType(precision + min(10, 31 - precision), scale) + + +@Sum.register # type: ignore +def infer_type( + arg: Union[ct.NumberType, ct.IntervalTypeBase], +) -> ct.DoubleType: + return ct.DoubleType() + + +class ToDate(Function): # pragma: no cover # pylint: disable=abstract-method + """ + Converts a date string to a date value. + """ + + +@ToDate.register # type: ignore +def infer_type( + expr: ct.StringType, + fmt: Optional[ct.StringType] = None, +) -> ct.DateType: + return ct.DateType() + + +class Transform(Function): + """ + transform(expr, func) - Transforms elements in an array + using the function. + """ + + @staticmethod + def compile_lambda(*args): + """ + Compiles the lambda function used by the `transform` Spark function so that + the lambda's expression can be evaluated to determine the result's type. + """ + from datajunction_server.sql.parsing import ( # pylint: disable=import-outside-toplevel + ast, + ) + + expr, func = args + available_identifiers = { + identifier.name: idx for idx, identifier in enumerate(func.identifiers) + } + columns = list( + func.expr.filter( + lambda x: isinstance(x, ast.Column) + and x.alias_or_name.name in available_identifiers, + ), + ) + for col in columns: + # The array element arg + if available_identifiers.get(col.alias_or_name.name) == 0: + col.add_type(expr.type.element.type) + + # The index arg (optional) + if available_identifiers.get(col.alias_or_name.name) == 1: + col.add_type(ct.IntegerType()) + + +@Transform.register # type: ignore +def infer_type( + expr: ct.ListType, + func: ct.PrimitiveType, +) -> ct.ColumnType: + return ct.ListType(element_type=func.expr.type) + + +class Trim(Function): + """ + Removes leading and trailing whitespace from a string value. + """ + + +@Trim.register +def infer_type(arg: ct.StringType) -> ct.StringType: + return ct.StringType() + + +class Unhex(Function): + """ + unhex(str) - Interprets each pair of characters in the input string as a + hexadecimal number and converts it to the byte that number represents. + The output is a binary string. + """ + + +@Unhex.register # type: ignore +def infer_type(arg: ct.StringType) -> ct.ColumnType: + return ct.BinaryType() + + +class Upper(Function): + """ + Converts a string value to uppercase. + """ + + +@Upper.register +def infer_type(arg: ct.StringType) -> ct.StringType: + return ct.StringType() + + +class Variance(Function): + """ + Computes the sample variance of the input column or expression. + """ + + is_aggregation = True + + +@Variance.register +def infer_type(arg: ct.NumberType) -> ct.DoubleType: + return ct.DoubleType() + + +class VarPop(Function): + """ + Computes the population variance of the input column or expression. + """ + + is_aggregation = True + + +@VarPop.register +def infer_type(arg: ct.NumberType) -> ct.DoubleType: + return ct.DoubleType() + + +class Week(Function): + """ + Returns the week number of the year of the input date value. + Note: Trino-only + """ + + +@Week.register +def infer_type(arg: Union[ct.StringType, ct.DateTimeBase]) -> ct.BigIntType: + return ct.BigIntType() + + +class Year(Function): + """ + Returns the year of the input date value. + """ + + +@Year.register +def infer_type( + arg: Union[ct.StringType, ct.DateTimeBase, ct.IntegerType], +) -> ct.BigIntType: + return ct.BigIntType() + + +################################################################################## +# Table Functions # +# https://spark.apache.org/docs/3.3.2/sql-ref-syntax-qry-select-tvf.html#content # +################################################################################## + + +class Explode(TableFunction): + """ + The Explode function is used to explode the specified array, + nested array, or map column into multiple rows. + The explode function will generate a new row for each + element in the specified column. + """ + + +@Explode.register +def infer_type( + arg: ct.ListType, +) -> List[ct.NestedField]: + return [arg.element] + + +@Explode.register +def infer_type( + arg: ct.MapType, +) -> List[ct.NestedField]: + return [arg.key, arg.value] + + +class Unnest(TableFunction): + """ + The unnest function is used to explode the specified array, + nested array, or map column into multiple rows. + It will generate a new row for each element in the specified column. + """ + + +@Unnest.register +def infer_type( + arg: ct.ListType, +) -> List[ct.NestedField]: + return [arg.element] # pragma: no cover + + +@Unnest.register +def infer_type( + arg: ct.MapType, +) -> List[ct.NestedField]: + return [arg.key, arg.value] + + +class FunctionRegistryDict(dict): + """ + Custom dictionary mapping for functions + """ + + def __getitem__(self, key): + """ + Returns a custom error about functions that haven't been implemented yet. + """ + try: + return super().__getitem__(key) + except KeyError as exc: + raise DJNotImplementedException( + f"The function `{key}` hasn't been implemented in " + "DJ yet. You can file an issue at https://github." + "com/DataJunction/dj/issues/new?title=Function+" + f"missing:+{key} to request it to be added, or use " + "the documentation at https://github.com/DataJunct" + "ion/dj/blob/main/docs/functions.rst to implement it.", + ) from exc + + +function_registry = FunctionRegistryDict() +for cls in Function.__subclasses__(): + snake_cased = re.sub(r"(? Iterator: + """ + Flattens `maybe_iterables` by descending into items that are Iterable + """ + + if not isinstance(maybe_iterables, (list, tuple, set, Iterator)): + return iter([maybe_iterables]) + return chain.from_iterable( + (flatten(maybe_iterable) for maybe_iterable in maybe_iterables) + ) + + +@dataclass +class CompileContext: + session: Session + exception: DJException + + +# typevar used for node methods that return self +# so the typesystem can correlate the self type with the return type +TNode = TypeVar("TNode", bound="Node") # pylint: disable=C0103 + + +class Node(ABC): + """Base class for all DJ AST nodes. + + DJ nodes are python dataclasses with the following patterns: + - Attributes are either + - PRIMITIVES (int, float, str, bool, None) + - iterable from (list, tuple, set) + - Enum + - descendant of `Node` + - Attributes starting with '_' are "obfuscated" and are not included in `children` + + """ + + parent: Optional["Node"] = None + parent_key: Optional[str] = None + + _is_compiled: bool = False + + def __post_init__(self): + self.add_self_as_parent() + + @property + def depth(self) -> int: + if self.parent is None: + return 0 + return self.parent.depth + 1 + + def clear_parent(self: TNode) -> TNode: + """ + Remove parent from the node + """ + self.parent = None + return self + + def set_parent(self: TNode, parent: "Node", parent_key: str) -> TNode: + """ + Add parent to the node + """ + self.parent = parent + self.parent_key = parent_key + return self + + def add_self_as_parent(self: TNode) -> TNode: + """ + Adds self as a parent to all children + """ + for name, child in self.fields( + flat=True, + nodes_only=True, + obfuscated=False, + nones=False, + named=True, + ): + child.set_parent(self, name) + return self + + def __setattr__(self, key: str, value: Any): + """ + Facilitates setting children using `.` syntax ensuring parent is attributed + """ + if key == "parent": + object.__setattr__(self, key, value) + return + + object.__setattr__(self, key, value) + for child in flatten(value): + if isinstance(child, Node) and not key.startswith("_"): + child.set_parent(self, key) + + def swap(self: TNode, other: "Node") -> TNode: + """ + Swap the Node for another + """ + if not (self.parent and self.parent_key): + return self + parent_attr = getattr(self.parent, self.parent_key) + if parent_attr is self: + setattr(self.parent, self.parent_key, other) + return self.clear_parent() + + new = [] + for iterable_type in (list, tuple, set): + if isinstance(parent_attr, iterable_type): + for element in parent_attr: + if self is element: + new.append(other) + else: + new.append(element) + new = iterable_type(new) + break + + setattr(self.parent, self.parent_key, new) + return self.clear_parent() + + def copy(self: TNode) -> TNode: + """ + Create a deep copy of the `self` + """ + return deepcopy(self) + + def get_nearest_parent_of_type( + self: "Node", + node_type: Type[TNode], + ) -> Optional[TNode]: + """ + Traverse up the tree until you find a node of `node_type` or hit the root + """ + if isinstance(self.parent, node_type): + return self.parent + if self.parent is None: + return None + return self.parent.get_nearest_parent_of_type(node_type) + + def get_furthest_parent( + self: "Node", + ) -> Optional[TNode]: + """ + Traverse up the tree until you find a node of `node_type` or hit the root + """ + if self.parent is None: + return None + curr_parent = self.parent + while True: + if curr_parent.parent is None: + return curr_parent + curr_parent = curr_parent.parent + + def flatten( + self, + obfuscated: bool = False, + named: bool = False, + ) -> Iterator["Node"]: + """ + Flatten the sub-ast of the node as an iterator + """ + seen = set() + + def _flatten(args): + parent_key = None + if named: + parent_key, node = args + else: + node = args + if id(node) not in seen: + yield (node if not named else (parent_key, node)) + seen.add(id(node)) + for child in chain( + *[ + _flatten(child) + for child in node.fields( + nodes_only=True, + flat=True, + named=named, + nones=False, + obfuscated=obfuscated, + ) + if id(child) not in seen + ] + ): + yield child + + return _flatten((self.parent_key, self) if named else self) + + # pylint: disable=R0913 + def fields( + self, + flat: bool = True, + nodes_only: bool = True, + obfuscated: bool = False, + nones: bool = False, + named: bool = False, + ) -> Iterator: + """ + Returns an iterator over fields of a node with particular filters + + Args: + flat: return a flattened iterator (if children are iterable) + nodes_only: do not yield children that are not Nodes (trumped by `obfuscated`) + obfuscated: yield fields that have leading underscores + (typically accessed via a property) + nones: yield values that are None + (optional fields without a value); trumped by `nodes_only` + named: yield pairs `(field name: str, field value)` + Returns: + Iterator: returns all children of a node given filters + and optional flattening (by default Iterator[Node]) + """ + + def make_child_generator(): + """ + Makes a generator enclosing self to return + not obfuscated fields (fields without starting `_`) + """ + for self_field in fields(self): + if ( + not self_field.name.startswith("_") if not obfuscated else True + ) and (self_field.name in self.__dict__): + value = self.__dict__[self_field.name] + values = [value] + if flat: + values = flatten(value) + for value in values: + if named: + yield (self_field.name, value) + else: + yield value + + # `iter`s used to satisfy mypy (`child_generator` type changes between generator, filter) + child_generator = iter(make_child_generator()) + + if nodes_only: + child_generator = iter( + filter( + lambda child: isinstance(child, Node) + if not named + else isinstance(child[1], Node), + child_generator, + ), + ) + + if not nones: + child_generator = iter( + filter( + lambda child: (child is not None) + if not named + else (child[1] is not None), + child_generator, + ), + ) # pylint: disable=C0301 + + return child_generator + + @property + def children(self) -> Iterator["Node"]: + """ + Returns an iterator of all nodes that are one + step from the current node down including through iterables + """ + return self.fields( + flat=True, + nodes_only=True, + obfuscated=False, + nones=False, + named=False, + ) + + def replace( # pylint: disable=invalid-name + self, + from_: "Node", + to: "Node", + compare: Optional[Callable[[Any, Any], bool]] = None, + times: int = -1, + copy: bool = True, + ): + """ + Replace a node `from_` with a node `to` in the subtree + """ + replacements = 0 + compare_ = (lambda a, b: a is b) if compare is None else compare + for node in self.flatten(): + if compare_(node, from_): + other = to.copy() if copy else to + if isinstance(from_, Table): + for ref in from_.ref_columns: + ref.add_table(other) + node.swap(other) + replacements += 1 + if replacements == times: + return + + def filter(self, func: Callable[["Node"], bool]) -> Iterator["Node"]: + """ + Find all nodes that `func` returns `True` for + """ + if func(self): + yield self + + for node in chain(*[child.filter(func) for child in self.children]): + yield node + + def contains(self, other: "Node") -> bool: + """ + Checks if the subtree of `self` contains the node + """ + return any(self.filter(lambda node: node is other)) + + def is_ancestor_of(self, other: Optional["Node"]) -> bool: + """ + Checks if `self` is an ancestor of the node + """ + return bool(other) and other.contains(self) + + def find_all(self, node_type: Type[TNode]) -> Iterator[TNode]: + """ + Find all nodes of a particular type in the node's sub-ast + """ + return self.filter(lambda n: isinstance(n, node_type)) # type: ignore + + def apply(self, func: Callable[["Node"], None]): + """ + Traverse ast and apply func to each Node + """ + func(self) + for child in self.children: + child.apply(func) + + def compare( + self, + other: "Node", + ) -> bool: + """ + Compare two ASTs for deep equality + """ + if type(self) != type(other): # pylint: disable=unidiomatic-typecheck + return False + if id(self) == id(other): + return True + return hash(self) == hash(other) + + def diff(self, other: "Node") -> List[Tuple["Node", "Node"]]: + """ + Compare two ASTs for differences and return the pairs of differences + """ + + def _diff(self, other: "Node"): + if self != other: + diffs.append((self, other)) + else: + for child, other_child in zip_longest(self.children, other.children): + _diff(child, other_child) + + diffs: List[Tuple["Node", "Node"]] = [] + _diff(self, other) + return diffs + + def similarity_score(self, other: "Node") -> float: + """ + Determine how similar two nodes are with a float score + """ + self_nodes = list(self.flatten()) + other_nodes = list(other.flatten()) + intersection = [ + self_node for self_node in self_nodes if self_node in other_nodes + ] + union = ( + [self_node for self_node in self_nodes if self_node not in intersection] + + [ + other_node + for other_node in other_nodes + if other_node not in intersection + ] + + intersection + ) + return len(intersection) / len(union) + + def __eq__(self, other) -> bool: + """ + Compares two nodes for "top level" equality. + + Checks for type equality and primitive field types for full equality. + Compares all others for type equality only. No recursing. + Note: Does not check (sub)AST. See `Node.compare` for comparing (sub)ASTs. + """ + return type(self) == type(other) and all( # pylint: disable=C0123 + s == o + if type(s) in PRIMITIVES # pylint: disable=C0123 + else type(s) == type(o) # pylint: disable=C0123 + for s, o in zip( + (self.fields(False, False, False, True)), + (other.fields(False, False, False, True)), + ) + ) + + def __hash__(self) -> int: + """ + Hash a node + """ + return hash( + tuple( + chain( + (type(self),), + self.fields( + flat=True, + nodes_only=False, + obfuscated=False, + nones=True, + named=False, + ), + ), + ), + ) + + @abstractmethod + def __str__(self) -> str: + """ + Get the string of a node + """ + + def compile(self, ctx: CompileContext): + """ + Compile a DJ Node. By default, we call compile on all immediate children of this node. + """ + if self._is_compiled: + return + for child in self.children: + if not child.is_compiled(): + child.compile(ctx) + child._is_compiled = True + self._is_compiled = True + + def is_compiled(self) -> bool: + """ + Checks whether a DJ AST Node is compiled + """ + return self._is_compiled + + +class DJEnum(str, Enum): + """ + A DJ AST enum + """ + + def __str__(self): + return self.value + + +@dataclass(eq=False) +class Aliasable(Node): + """ + A mixin for Nodes that are aliasable + """ + + alias: Optional["Name"] = None + as_: Optional[bool] = None + + def set_alias(self: TNode, alias: Optional["Name"]) -> TNode: + self.alias = alias + return self + + def set_as(self: TNode, as_: bool) -> TNode: + self.as_ = as_ + return self + + @property + def columns(self): + """ + Returns a list of self if aliased or named else an empty list + """ + return [self] + + @property + def alias_or_name(self) -> "Name": + if self.alias is not None: + return self.alias + elif isinstance(self, Named): + return self.name + else: + raise DJParseException("Node has no alias or name.") + + +AliasedType = TypeVar("AliasedType", bound=Node) # pylint: disable=C0103 + + +@dataclass(eq=False) +class Alias(Aliasable, Generic[AliasedType]): + """ + Wraps node types with an alias + """ + + child: AliasedType = field(default_factory=Node) + + def __str__(self) -> str: + as_ = " AS " if self.as_ else " " + return f"{self.child}{as_}{self.alias}" + + def is_aggregation(self) -> bool: + return isinstance(self.child, Expression) and self.child.is_aggregation() + + @property + def type(self) -> ColumnType: + return self.child.type + + @property + def columns(self): + """ + Returns a list of self if aliased or named else an empty list + """ + return [self] + + +TExpression = TypeVar("TExpression", bound="Expression") # pylint: disable=C0103 + + +@dataclass(eq=False) +class Expression(Node): + """ + An expression type simply for type checking + """ + + parenthesized: Optional[bool] = field(init=False, default=None) + + @property + def type(self) -> Union[ColumnType, List[ColumnType]]: + """ + Return the type of the expression + """ + + @property + def columns(self): + """ + Returns a list of self if aliased or named else an empty list + """ + return [] + + def is_aggregation(self) -> bool: + """ + Determines whether an Expression is an aggregation or not + """ + return all( + [ + child.is_aggregation() + for child in self.children + if isinstance(child, Expression) + ] + or [False], + ) + + def set_alias(self: TExpression, alias: "Name") -> Alias[TExpression]: + return Alias(child=self).set_alias(alias) + + +@dataclass(eq=False) +class Name(Node): + """ + The string name specified in sql with quote style + """ + + name: str + quote_style: str = "" + namespace: Optional["Name"] = None + + def __post_init__(self): + if isinstance(self.name, Name): + self.quote_style = self.quote_style or self.name.quote_style + self.namespace = self.namespace or self.name.namespace + self.name = self.name.name + + def __str__(self) -> str: + return self.identifier(True) + + @property + def names(self) -> List["Name"]: + namespace = [self] + name = self + while name.namespace: + namespace.append(name.namespace) + name = name.namespace + return namespace[::-1] + + def identifier(self, quotes: bool = True) -> str: + """ + Yield a string of all the names making up + the name with or without quotes + """ + quote_style = "" if not quotes else self.quote_style + namespace = str(self.namespace) + "." if self.namespace else "" + return ( + f"{namespace}{quote_style}{self.name}{quote_style}" # pylint: disable=C0301 + ) + + +TNamed = TypeVar("TNamed", bound="Named") # pylint: disable=C0103 + + +@dataclass(eq=False) # type: ignore +class Named(Node): + """ + An Expression that has a name + """ + + name: Name + + @staticmethod + def namespaces_intersect( + namespace_a: List[Name], + namespace_b: List[Name], + quotes: bool = False, + ): + return all( + na.name == nb.name if not quotes else str(na) == str(nb) + for na, nb in zip(reversed(namespace_a), reversed(namespace_b)) + ) + + @property + def names(self) -> List[Name]: + return self.name.names + + @property + def namespace(self) -> List[Name]: + return self.names[:-1] + + @property + def columns(self): + """ + Returns a list of self if aliased or named else an empty list + """ + return [self] + + def identifier(self, quotes: bool = True) -> str: + if quotes: + return str(self.name) + + return ".".join( + ( + *(name.name for name in self.namespace), + self.name.name, + ), + ) + + @property + def alias_or_name(self) -> "Name": + return self.name + + +@dataclass(eq=False) +class DefaultName(Name): + name: str = "" + + def __bool__(self) -> bool: + return False + + +@dataclass(eq=False) +class UnNamed(Named): + name: Name = field(default_factory=DefaultName) + + +@dataclass(eq=False) +class Column(Aliasable, Named, Expression): + """ + Column used in statements + """ + + _table: Optional[Union[Aliasable, "TableExpression"]] = field( + repr=False, + default=None, + ) + _is_struct_ref: bool = False + _type: Optional["ColumnType"] = field(repr=False, default=None) + _expression: Optional[Expression] = field(repr=False, default=None) + _is_compiled: bool = False + + @property + def type(self): + if self._type: + return self._type + # Column was derived from some other expression we can get the type of + if self.expression: + self.add_type(self.expression.type) + return self.expression.type + + parent_expr = f"in {self.parent}" if self.parent else "that has no parent" + raise DJParseException(f"Cannot resolve type of column {self} {parent_expr}") + + def add_type(self, type_: ColumnType) -> "Column": + """ + Add a referenced type + """ + self._type = type_ + return self + + @property + def expression(self) -> Optional[Expression]: + """ + Return the Expression this node points to in a subquery + """ + return self._expression + + def namespace_table(self): + """ + Turns the column's namespace into a table. + """ + if self.name.namespace: + self._table = Table(name=self.name.namespace) + self.name.namespace = None + self._table.parent = self + self._table.parent_key = "_table" + + def add_expression(self, expression: "Expression") -> "Column": + """ + Add a referenced expression where the column came from + """ + self._expression = expression + return self + + def set_struct_ref(self): + """ + Marks this column as a struct dereference. This implies that we treat the name + and namespace values on this object as struct column and struct subscript values. + """ + self._is_struct_ref = True + + def add_table(self, table: "TableExpression"): + self._table = table + + @property + def table(self) -> Optional["TableExpression"]: + """ + Return the table the column was referenced from + """ + return self._table + + @property + def children(self) -> Iterator[Node]: + if self.table and self.table.parent is self: + return chain(super().children, (self.table,)) + return super().children + + @property + def is_api_column(self) -> bool: + """ + Is the column added from the api? + """ + return self._api_column + + def set_api_column(self, api_column: bool = False) -> "Column": + """ + Set the api column flag + """ + self._api_column = api_column + return self + + def use_alias_as_name(self) -> "Column": + """Use the column's alias as its name""" + self.name = self.alias + self.alias = None + return self + + def is_compiled(self): + return self._is_compiled or (self.table and self._type) + + def find_table_sources( + self, + ctx: CompileContext, + ) -> List["TableExpression"]: + # flake8: noqa + """ + Find all tables that this column could have originated from. + """ + query = cast( + Query, + self.get_nearest_parent_of_type(Query), + ) + direct_tables = list( + filter( + lambda tbl: tbl.in_from_or_lateral() + and tbl.get_nearest_parent_of_type(Query) is query, + query.find_all(TableExpression), + ), + ) + for table in direct_tables: + if not table.is_compiled(): + table.compile(ctx) + + namespace = ( + self.name.namespace.identifier(False) if self.name.namespace else "" + ) # a.x -> a + found = [] + + # Go through TableExpressions directly on the AST first and collect all + # possible origins for this column. There may be more than one if the column + # is not namespaced. + for table in direct_tables: + if not namespace or table.alias_or_name.identifier(False) == namespace: + if table.add_ref_column(self, ctx): + found.append(table) + + # The column's namespace may match the name of a column on the table, which + # implies that the column on the table is likely a struct and the dereferencing + # will happen on the struct object + for col in table.columns: + if col.alias_or_name.name == namespace: + table.add_ref_column(self, ctx) + + if found: + return found + + if not query.in_from_or_lateral(): + correlation_tables = list( + filter( + lambda tbl: tbl.in_from_or_lateral() + and query.is_ancestor_of(tbl.get_nearest_parent_of_type(Query)), + query.find_all(TableExpression), + ), + ) + for table in correlation_tables: + if not namespace or table.alias_or_name.identifier(False) == namespace: + if table.add_ref_column(self, ctx): + found.append(table) + + if found: + return found + + # Check for ctes + alpha_query = self.get_furthest_parent() + if isinstance(alpha_query, Query) and alpha_query.ctes: + for table in alpha_query.ctes: + if table.alias_or_name.identifier(False) == namespace: + if table.add_ref_column(self, ctx): + found.append(table) + + # If nothing was found in the initial AST, traverse through dimensions graph + # to find another table in DJ that could be its origin + to_process = collections.deque(direct_tables) + while to_process: + current_table = to_process.pop() + if ( + not namespace + or current_table.alias_or_name.identifier(False) == namespace + ): + if current_table.add_ref_column(self, ctx): + found.append(current_table) + return found + + # If the table has a DJ node, check to see if the DJ node has dimensions, + # which would link us to new nodes to search for this column in + if isinstance(current_table, Table) and current_table.dj_node: + for dj_col in current_table.dj_node.columns: + if dj_col.dimension: + new_table = Table( + name=to_namespaced_name(dj_col.dimension.name), + _dj_node=dj_col.dimension.current, + ) + new_table._columns = [ + Column( + name=Name(col.name), + _type=col.type, + _table=new_table, + ) + for col in dj_col.dimension.current.columns + ] + to_process.append(new_table) + return found + + def compile(self, ctx: CompileContext): + """ + Compile a column. + Determines the table from which a column is from. + """ + + if self.is_compiled(): + return + + # check if the column was already given a table + if self.table and isinstance(self.table.parent, Column): + self.table.add_ref_column(self, ctx) + else: + found_sources = self.find_table_sources(ctx) + if len(found_sources) < 1: + ctx.exception.errors.append( + DJError( + code=ErrorCode.INVALID_COLUMN, + message=f"Column `{self}` does not exist on any valid table.", + ), + ) + return + + if len(found_sources) > 1: + ctx.exception.errors.append( + DJError( + code=ErrorCode.INVALID_COLUMN, + message=f"Column `{self.name.name}` found in multiple tables. Consider using fully qualified name.", + ), + ) + return + + source_table = cast(TableExpression, found_sources[0]) + source_table.add_ref_column(self, ctx) + self._is_compiled = True + + @property + def struct_column_name(self) -> str: + """If this is a struct reference, the struct type's column name""" + return self.namespace[0].name + + @property + def struct_subscript(self) -> str: + """If this is a struct reference, the struct type's field name""" + return self.name.name + + def __str__(self) -> str: + as_ = " AS " if self.as_ else " " + alias = "" if not self.alias else f"{as_}{self.alias}" + if self.table is not None and not isinstance(self.table, FunctionTable): + name = ( + self.struct_column_name + "." + self.struct_subscript + if self._is_struct_ref + else self.name.name + ) + ret = f"{self.name.quote_style}{name}{self.name.quote_style}" + if table_name := self.table.alias_or_name: + ret = table_name.identifier() + "." + ret + else: + ret = str(self.name) + if self.parenthesized: + ret = f"({ret})" + return ret + alias + + @property + def is_struct_ref(self): + return self._is_struct_ref + + +@dataclass(eq=False) +class Wildcard(Named, Expression): + """ + Wildcard or '*' expression + """ + + name: Name = field(init=False, repr=False, default=Name("*")) + _table: Optional["Table"] = field(repr=False, default=None) + + @property + def table(self) -> Optional["Table"]: + """ + Return the table the column was referenced from if there's one + """ + return self._table + + def add_table(self, table: "Table") -> "Wildcard": + """ + Add a referenced table + """ + if self._table is None: + self._table = table + return self + + def __str__(self) -> str: + return "*" + + @property + def type(self) -> ColumnType: + return WildcardType() + + +@dataclass(eq=False) +class TableExpression(Aliasable, Expression): + """ + A type for table expressions + """ + + column_list: List[Column] = field(default_factory=list) + _columns: List[Expression] = field( + default_factory=list, + ) # all those expressions that can be had from the table; usually derived from dj node metadata for Table + # ref (referenced) columns are columns used elsewhere from this table + _ref_columns: List[Column] = field(init=False, repr=False, default_factory=list) + + @property + def columns(self) -> List[Expression]: + """ + Return the columns named in this table + """ + col_list_names = {col.name.name for col in self.column_list} + return [ + col + for col in self._columns + if isinstance(col, (Aliasable, Named)) + and (col.alias_or_name.name in col_list_names if col_list_names else True) + ] + + @property + def ref_columns(self) -> Set[Column]: + """ + Return the columns referenced from this table + """ + return self._ref_columns + + def add_ref_column( + self, + column: Column, + ctx: Optional[CompileContext] = None, + ) -> bool: + """ + Add column referenced from this table + returning True if the table has the column + and False otherwise + """ + if not self._columns: + if ctx is None: + raise DJErrorException( + "Uncompiled Table expression requested to " + "add Column ref without a compilation context.", + ) + self.compile(ctx) + + # For table-valued functions, add the list of columns that gets + # returned as reference columns and compile them + if isinstance(self, FunctionTable): + if ( + not self.alias + and not column.name.namespace + or ( + self.alias + and column.name.namespace + and self.alias == column.name.namespace + ) + ): + for col in self.column_list: + if column.name.name == col.alias_or_name.name: + self._ref_columns.append(column) + column.add_table(self) + column.add_expression(col) + column.add_type(col.type) + return True + + for col in self.columns: + if isinstance(col, (Aliasable, Named)): + if column.name.name == col.alias_or_name.name: + self._ref_columns.append(column) + column.add_table(self) + column.add_expression(col) + column.add_type(col.type) + return True + + # For struct types we can additionally check if there's a column that matches + # the search column's namespace and if there's a nested field that matches the + # search column's name + if isinstance(col.type, StructType): + # struct column name + column_namespace = ".".join( + [name.name for name in column.namespace], + ) + if column_namespace == col.alias_or_name.identifier(False): + for type_field in col.type.fields: + if type_field.name.name == column.name.name: + self._ref_columns.append(column) + column.set_struct_ref() + column.add_table(self) + column.add_expression(col) + column.add_type(type_field.type) + return True + return False + + def is_compiled(self) -> bool: + return bool(self._columns) or self._is_compiled + + def in_from_or_lateral(self) -> bool: + """ + Determines if the table expression is referenced in a From clause + """ + + if from_ := self.get_nearest_parent_of_type(From): + return from_.get_nearest_parent_of_type( + Select, + ) is self.get_nearest_parent_of_type(Select) + if lateral := self.get_nearest_parent_of_type(LateralView): + return lateral.get_nearest_parent_of_type( + Select, + ) is self.get_nearest_parent_of_type(Select) + return False + + +@dataclass(eq=False) +class Table(TableExpression, Named): + """ + A type for tables + """ + + _dj_node: Optional[DJNode] = field(repr=False, default=None) + + @property + def dj_node(self) -> Optional[DJNode]: + """ + Return the dj_node referenced by this table + """ + return self._dj_node + + def set_dj_node(self, dj_node: DJNode) -> "Table": + """ + Set dj_node referenced by this table + """ + self._dj_node = dj_node + return self + + def __str__(self) -> str: + table_str = str(self.name) + if self.alias: + as_ = " AS " if self.as_ else " " + table_str += f"{as_}{self.alias}" + return table_str + + def is_compiled(self) -> bool: + return super().is_compiled() and (self.dj_node is not None) + + def set_alias(self: TNode, alias: "Name") -> TNode: + self.alias = alias + for col in self._columns: + col.table.alias = self.alias + return self + + def compile(self, ctx: CompileContext): + # things we can validate here: + # - if the node is a dimension in a groupby, is it joinable? + self._is_compiled = True + try: + if not self.dj_node: + dj_node = get_dj_node( + ctx.session, + self.identifier(quotes=False), + {DJNodeType.SOURCE, DJNodeType.TRANSFORM, DJNodeType.DIMENSION}, + ) + self.set_dj_node(dj_node) + self._columns = [ + Column(Name(col.name), _type=col.type, _table=self) + for col in self.dj_node.columns + ] + except DJErrorException as exc: + ctx.exception.errors.append(exc.dj_error) + + +class Operation(Expression): + """ + A type to overarch types that operate on other expressions + """ + + +# pylint: disable=C0103 +class UnaryOpKind(DJEnum): + """ + The accepted unary operations + """ + + Exists = "EXISTS" + Not = "NOT" + + +@dataclass(eq=False) +class UnaryOp(Operation): + """ + An operation that operates on a single expression + """ + + op: UnaryOpKind + expr: Expression + + def __str__(self) -> str: + ret = f"{self.op} {self.expr}" + if self.parenthesized: + return f"({ret})" + return ret + + @property + def type(self) -> ColumnType: + type_ = self.expr.type + + def raise_unop_exception(): + raise DJParseException( + "Incompatible type in unary operation " + f"{self}. Got {type} in {self}.", + ) + + if self.op == UnaryOpKind.Not: + if isinstance(type_, BooleanType): + return type_ + raise_unop_exception() + if self.op == UnaryOpKind.Exists: + if isinstance(type_, BooleanType): + return type_ + raise_unop_exception() + + raise DJParseException(f"Unary operation {self.op} not supported!") + + +# pylint: disable=C0103 +class BinaryOpKind(DJEnum): + """ + The DJ AST accepted binary operations + """ + + And = "AND" + LogicalAnd = "&&" + Or = "OR" + LogicalOr = "||" + Is = "IS" + Eq = "=" + NotEq = "<>" + NotEquals = "!=" + Gt = ">" + Lt = "<" + GtEq = ">=" + LtEq = "<=" + BitwiseOr = "|" + BitwiseAnd = "&" + BitwiseXor = "^" + Multiply = "*" + Divide = "/" + Plus = "+" + Minus = "-" + Modulo = "%" + + +@dataclass(eq=False) +class BinaryOp(Operation): + """ + Represents an operation that operates on two expressions + """ + + op: BinaryOpKind + left: Expression + right: Expression + use_alias_as_name: Optional[bool] = False + + @classmethod + def And( # pylint: disable=invalid-name,keyword-arg-before-vararg + cls, + left: Expression, + right: Optional[Expression] = None, + *rest: Expression, + ) -> Union["BinaryOp", Expression]: + """ + Create a BinaryOp of kind BinaryOpKind.Eq rolling up all expressions + """ + if right is None: # pragma: no cover + return left + return reduce( + lambda left, right: BinaryOp( + BinaryOpKind.And, + left, + right, + ), + (left, right, *rest), + ) + + @classmethod + def Eq( # pylint: disable=invalid-name + cls, + left: Expression, + right: Optional[Expression], + use_alias_as_name: Optional[bool] = False, + ) -> Union["BinaryOp", Expression]: + """ + Create a BinaryOp of kind BinaryOpKind.Eq + """ + if right is None: # pragma: no cover + return left + return BinaryOp( + BinaryOpKind.Eq, + left, + right, + use_alias_as_name=use_alias_as_name, + ) + + def __str__(self) -> str: + left, right = self.left, self.right + if self.use_alias_as_name: + if isinstance(self.right, Column) and self.right.alias: + right = self.right.copy().use_alias_as_name() + if isinstance(self.left, Column) and self.left.alias: + left = self.left.copy().use_alias_as_name() + ret = f"{left} {self.op} {right}" + + if self.parenthesized: + return f"({ret})" + return ret + + @property + def type(self) -> ColumnType: + kind = self.op + left_type = self.left.type + right_type = self.right.type + + def raise_binop_exception(): + raise DJParseException( + "Incompatible types in binary operation " + f"{self}. Got left {left_type}, right {right_type}.", + ) + + numeric_types = { + type_: idx + for idx, type_ in enumerate( + [ + str(DoubleType()), + str(FloatType()), + str(BigIntType()), + str(IntegerType()), + ], + ) + } + + def resolve_numeric_types_binary_operations( + left: ColumnType, + right: ColumnType, + ): + if not left.is_compatible(right): + raise_binop_exception() + if str(left) in numeric_types and str(right) in numeric_types: + if str(left) == str(right): + return left + if numeric_types[str(left)] > numeric_types[str(right)]: + return right + return left + return left + + BINOP_TYPE_COMBO_LOOKUP: Dict[ # pylint: disable=C0103 + BinaryOpKind, + Callable[[ColumnType, ColumnType], ColumnType], + ] = { + BinaryOpKind.And: lambda left, right: BooleanType(), + BinaryOpKind.Or: lambda left, right: BooleanType(), + BinaryOpKind.Is: lambda left, right: BooleanType(), + BinaryOpKind.Eq: lambda left, right: BooleanType(), + BinaryOpKind.NotEq: lambda left, right: BooleanType(), + BinaryOpKind.NotEquals: lambda left, right: BooleanType(), + BinaryOpKind.Gt: lambda left, right: BooleanType(), + BinaryOpKind.Lt: lambda left, right: BooleanType(), + BinaryOpKind.GtEq: lambda left, right: BooleanType(), + BinaryOpKind.LtEq: lambda left, right: BooleanType(), + BinaryOpKind.BitwiseOr: lambda left, right: IntegerType() + if str(left) == str(IntegerType()) and str(right) == str(IntegerType()) + else raise_binop_exception(), + BinaryOpKind.BitwiseAnd: lambda left, right: IntegerType() + if str(left) == str(IntegerType()) and str(right) == str(IntegerType()) + else raise_binop_exception(), + BinaryOpKind.BitwiseXor: lambda left, right: IntegerType() + if str(left) == str(IntegerType()) and str(right) == str(IntegerType()) + else raise_binop_exception(), + BinaryOpKind.Multiply: resolve_numeric_types_binary_operations, + BinaryOpKind.Divide: resolve_numeric_types_binary_operations, + BinaryOpKind.Plus: resolve_numeric_types_binary_operations, + BinaryOpKind.Minus: resolve_numeric_types_binary_operations, + BinaryOpKind.Modulo: lambda left, right: IntegerType() + if str(left) == str(IntegerType()) and str(right) == str(IntegerType()) + else raise_binop_exception(), + } + return BINOP_TYPE_COMBO_LOOKUP[kind](left_type, right_type) + + def compile(self, ctx: CompileContext): + """ + Compile a DJ Node. By default, we call compile on all immediate children of this node. + """ + if self._is_compiled: + return + for child in self.children: + if not child.is_compiled(): + child.compile(ctx) + child._is_compiled = True + self._is_compiled = True + + +@dataclass(eq=False) +class FrameBound(Expression): + """ + Represents frame bound in a window function + """ + + start: str + stop: str + + def __str__(self) -> str: + return f"{self.start} {self.stop}" + + +@dataclass(eq=False) +class Frame(Expression): + """ + Represents frame in window function + """ + + frame_type: str + start: FrameBound + end: Optional[FrameBound] = None + + def __str__(self) -> str: + end = f" AND {self.end}" if self.end else "" + between = " BETWEEN" if self.end else "" + return f"{self.frame_type}{between} {self.start}{end}" + + +@dataclass(eq=False) +class Over(Expression): + """ + Represents a function used in a statement + """ + + partition_by: List[Expression] = field(default_factory=list) + order_by: List["SortItem"] = field(default_factory=list) + window_frame: Optional[Frame] = None + + def __str__(self) -> str: + partition_by = ( # pragma: no cover + " PARTITION BY " + ", ".join(str(exp) for exp in self.partition_by) + if self.partition_by + else "" + ) + order_by = ( + " ORDER BY " + ", ".join(str(exp) for exp in self.order_by) + if self.order_by + else "" + ) + consolidated_by = "\n".join( + po_by for po_by in (partition_by, order_by) if po_by + ) + window_frame = f" {self.window_frame}" if self.window_frame else "" + return f"OVER ({consolidated_by}{window_frame})" + + +@dataclass(eq=False) +class Function(Named, Operation): + """ + Represents a function used in a statement + """ + + args: List[Expression] = field(default_factory=list) + quantifier: str = "" + over: Optional[Over] = None + + def __new__( + cls, + name: Name, + args: List[Expression], + quantifier: str = "", + over: Optional[Over] = None, + ): + # Check if function is a table-valued function + if ( + not quantifier + and over is None + and name.name.upper() in table_function_registry + ): + return FunctionTable(name, args=args) + + # If not, create a new Function object + return super().__new__(cls) + + def __getnewargs__(self): + return self.name, self.args + + def __deepcopy__(self, memodict): + return self + + def __str__(self) -> str: + if self.name.name.upper() in function_registry and self.is_runtime(): + return self.function().substitute() + + over = f" {self.over} " if self.over else "" + quantifier = f" {self.quantifier} " if self.quantifier else "" + ret = ( + f"{self.name}({quantifier}{', '.join(str(arg) for arg in self.args)}){over}" + ) + if self.parenthesized: + ret = f"({ret})" + return ret + + def function(self): + return function_registry[self.name.name.upper()] + + def is_aggregation(self) -> bool: + return self.function().is_aggregation + + def is_runtime(self) -> bool: + return self.function().is_runtime + + @property + def type(self) -> ColumnType: + return self.function().infer_type(*self.args) + + def compile(self, ctx: CompileContext): + """ + Compile a function + """ + self._is_compiled = True + for arg in self.args: + if not arg.is_compiled(): + arg.compile(ctx) + arg._is_compiled = True + + self.function().compile_lambda(*self.args) + + for child in self.children: + if not child.is_compiled(): + child.compile(ctx) + child._is_compiled = True + + +class Value(Expression): + """ + Base class for all values number, string, boolean + """ + + def is_aggregation(self) -> bool: + return True + + +@dataclass(eq=False) +class Null(Value): + """ + Null value + """ + + def __str__(self) -> str: + return "NULL" + + @property + def type(self) -> ColumnType: + return NullType() + + +@dataclass(eq=False) +class Number(Value): + """ + Number value + """ + + value: Union[float, int, decimal.Decimal] + _type: Optional[IntegerBase] = None + + def __post_init__(self): + super().__post_init__() + + if ( + not isinstance(self.value, float) + and not isinstance(self.value, int) + and not isinstance(self.value, decimal.Decimal) + ): + cast_exceptions = [] + numeric_types = [int, float, decimal.Decimal] + for cast_type in numeric_types: + try: + self.value = cast_type(self.value) + break + except (ValueError, OverflowError) as exception: + cast_exceptions.append(exception) + if len(cast_exceptions) >= len(numeric_types): + raise DJException(message="Not a valid number!") + + def __str__(self) -> str: + return str(self.value) + + @property + def type(self) -> ColumnType: + """ + Determine the type of the numeric expression. + """ + # We won't assume that anyone wants SHORT by default + if isinstance(self.value, int): + check_types = (self._type,) if self._type else (IntegerType(), BigIntType()) + for integer_type in check_types: + if integer_type.check_bounds(self.value): + return integer_type + + raise DJParseException( + f"No Integer type of {check_types} can hold the value {self.value}.", + ) + # + # # Arbitrary-precision floating point + # if isinstance(self.value, decimal.Decimal): + # return DecimalType.parse(self.value) + + # Double-precision floating point + if not (1.18e-38 <= abs(self.value) <= 3.4e38): + return DoubleType() + + # Single-precision floating point + return FloatType() + + +@dataclass(eq=False) +class String(Value): + """ + String value + """ + + value: str + + def __str__(self) -> str: + return self.value + + @property + def type(self) -> ColumnType: + return StringType() + + +@dataclass(eq=False) +class Boolean(Value): + """ + Boolean True/False value + """ + + value: bool + + def __str__(self) -> str: + return str(self.value) + + @property + def type(self) -> ColumnType: + return BooleanType() + + +@dataclass(eq=False) +class IntervalUnit(Value): + """ + Interval unit value + """ + + unit: str + value: Optional[Number] = None + + def __str__(self) -> str: + return f"{self.value or ''} {self.unit}" + + +@dataclass(eq=False) +class Interval(Value): + """ + Interval value + """ + + from_: List[IntervalUnit] + to: Optional[IntervalUnit] = None + + def __str__(self) -> str: + to = f"TO {self.to}" if self.to else "" + return f"INTERVAL {' '.join(str(interval) for interval in self.from_)} {to}" + + @property + def type(self) -> ColumnType: + """ + Determine the type of the interval expression. + """ + units = ["YEAR", "MONTH", "DAY", "HOUR", "MINUTE", "SECOND"] + years_months_units = {"YEAR", "MONTH"} + days_seconds_units = {"DAY", "HOUR", "MINUTE", "SECOND"} + + if all(unit.unit in years_months_units for unit in self.from_): + if self.to.unit is None or self.to.unit in years_months_units: + # If all the units in the from_ list are YEAR or MONTH, the interval is a YearMonthInterval + return YearMonthIntervalType( + sorted(self.from_, key=lambda u: units.index(u))[0], + self.to, + ) + elif all(unit.unit in days_seconds_units for unit in self.from_): + if self.to.unit is None or self.to.unit in days_seconds_units: + # If the to_ attribute is None or its unit is DAY, HOUR, MINUTE, or SECOND, the interval is a DayTimeInterval + return DayTimeIntervalType( + sorted(self.from_, key=lambda u: units.index(u))[0], + self.to, + ) + raise DJParseException(f"Invalid interval type specified in {self}.") + + +@dataclass(eq=False) +class Struct(Value): + """ + Struct value + """ + + values: List[Aliasable] + + def __str__(self): + inner = ", ".join(str(value) for value in self.values) + return f"STRUCT({inner})" + + +@dataclass(eq=False) +class Predicate(Operation): + """ + Represents a predicate + """ + + negated: bool = False + + @property + def type(self) -> ColumnType: + return BooleanType() + + +@dataclass(eq=False) +class Between(Predicate): + """ + A between statement + """ + + expr: Expression = field(default_factory=Expression) + low: Expression = field(default_factory=Expression) + high: Expression = field(default_factory=Expression) + + def __str__(self) -> str: + not_ = "NOT " if self.negated else "" + between = f"{not_}{self.expr} BETWEEN {self.low} AND {self.high}" + if self.parenthesized: + between = f"({between})" + return between + + @property + def type(self) -> ColumnType: + expr_type = self.expr.type + low_type = self.low.type + high_type = self.high.type + if expr_type == low_type == high_type: + return BooleanType() + raise DJParseException( + f"BETWEEN expects all elements to have the same type got " + f"{expr_type} BETWEEN {low_type} AND {high_type} in {self}.", + ) + + +@dataclass(eq=False) +class In(Predicate): + """ + An in expression + """ + + expr: Expression = field(default_factory=Expression) + source: Union[List[Expression], "Select"] = field(default_factory=Expression) + + def __str__(self) -> str: + not_ = "NOT " if self.negated else "" + source = ( + str(self.source) + if isinstance(self.source, Select) + else "(" + ", ".join(str(exp) for exp in self.source) + ")" + ) + return f"{self.expr} {not_}IN {source}" + + +@dataclass(eq=False) +class Rlike(Predicate): + """ + A regular expression match statement + """ + + expr: Expression = field(default_factory=Expression) + pattern: Expression = field(default_factory=Expression) + + def __str__(self) -> str: + not_ = "NOT " if self.negated else "" + return f"{not_}{self.expr} RLIKE {self.pattern}" + + +@dataclass(eq=False) +class Like(Predicate): + """ + A string pattern matching statement + """ + + expr: Expression = field(default_factory=Expression) + quantifier: str = "" + patterns: List[Expression] = field(default_factory=list) + escape_char: Optional[str] = None + case_sensitive: Optional[bool] = True + + def __str__(self) -> str: + not_ = "NOT " if self.negated else "" + if self.quantifier: # quantifier means a pattern with multiple elements + pattern = f'({", ".join(str(p) for p in self.patterns)})' + else: + pattern = self.patterns + escape_char = f" ESCAPE '{self.escape_char}'" if self.escape_char else "" + quantifier = f"{self.quantifier} " if self.quantifier else "" + like_type = "LIKE" if self.case_sensitive else "ILIKE" + return f"{not_}{self.expr} {like_type} {quantifier}{pattern}{escape_char}" + + @property + def type(self) -> ColumnType: + expr_type = self.expr.type + if expr_type == StringType(): + return BooleanType() + raise DJParseException( + f"Incompatible type for {self}: {expr_type}. Expected STR", + ) + + +@dataclass(eq=False) +class IsNull(Predicate): + """ + A null check statement + """ + + expr: Expression = field(default_factory=Expression) + + def __str__(self) -> str: + not_ = "NOT " if self.negated else "" + return f"{self.expr} IS {not_}NULL" + + @property + def type(self) -> ColumnType: + return BooleanType() + + +@dataclass(eq=False) +class IsBoolean(Predicate): + """ + A boolean check statement + """ + + expr: Expression = field(default_factory=Expression) + value: str = "UNKNOWN" + + def __str__(self) -> str: + not_ = "NOT " if self.negated else "" + return f"{self.expr} IS {not_}{self.value}" + + @property + def type(self) -> ColumnType: + return BooleanType() + + +@dataclass(eq=False) +class IsDistinctFrom(Predicate): + """ + A distinct from check statement + """ + + expr: Expression = field(default_factory=Expression) + right: Expression = field(default_factory=Expression) + + def __str__(self) -> str: + not_ = "NOT " if self.negated else "" + return f"{self.expr} IS {not_}DISTINCT FROM {self.right}" + + @property + def type(self) -> ColumnType: + return BooleanType() + + +@dataclass(eq=False) +class Case(Expression): + """ + A case statement of branches + """ + + expr: Optional[Expression] = None + conditions: List[Expression] = field(default_factory=list) + else_result: Optional[Expression] = None + operand: Optional[Expression] = None + results: List[Expression] = field(default_factory=list) + + def __str__(self) -> str: + branches = "\n\tWHEN ".join( + f"{(cond)} THEN {(result)}" + for cond, result in zip(self.conditions, self.results) + ) + else_ = f"ELSE {(self.else_result)}" if self.else_result else "" + expr = "" if self.expr is None else f" {self.expr} " + ret = f"""CASE {expr} + WHEN {branches} + {else_} + END""" + if self.parenthesized: + return f"({ret})" + return ret + + def is_aggregation(self) -> bool: + return all(result.is_aggregation() for result in self.results) and ( + self.else_result.is_aggregation() if self.else_result else True + ) + + @property + def type(self) -> ColumnType: + result_types = [ + res.type + for res in self.results + ([self.else_result] if self.else_result else []) + if res.type + ] + if not all(result_types[0].is_compatible(res) for res in result_types): + raise DJParseException( + f"Not all the same type in CASE! Found: {', '.join([str(type_) for type_ in result_types])}", + ) + return result_types[0] + + +@dataclass(eq=False) +class Subscript(Expression): + """ + Represents a subscript expression + """ + + expr: Expression + index: Expression + + def __str__(self) -> str: + return f"{self.expr}[{self.index}]" + + @property + def type(self) -> ColumnType: + if isinstance(self.expr.type, MapType): + type_ = cast(MapType, self.expr.type) + return type_.value.type + return cast(ListType, self.expr.type).element.type + + +@dataclass(eq=False) +class Lambda(Expression): + """ + Represents a lambda expression + """ + + identifiers: List[Named] + expr: Expression + + def __str__(self) -> str: + if len(self.identifiers) == 1: + id_str = self.identifiers[0] + else: + id_str = "(" + ", ".join(str(iden) for iden in self.identifiers) + ")" + return f"{id_str} -> {self.expr}" + + @property + def type(self) -> Union[ColumnType, List[ColumnType]]: + """ + The return type of the lambda function + """ + return self.expr.type + + +@dataclass(eq=False) +class JoinCriteria(Node): + """ + Represents the criteria for a join relation in a FROM clause + """ + + on: Optional[Expression] = None + using: Optional[List[Named]] = None + + def __str__(self) -> str: + if self.on: + return f"ON {self.on}" + else: + id_list = ", ".join(str(iden) for iden in self.using) + return f"USING ({id_list})" + + +@dataclass(eq=False) +class Join(Node): + """ + Represents a join relation in a FROM clause + """ + + join_type: str + right: Expression + criteria: Optional[JoinCriteria] = None + lateral: bool = False + natural: bool = False + + def __str__(self) -> str: + parts = [] + if self.natural: + parts.append("NATURAL ") + if self.join_type: + parts.append(f"{self.join_type} ") + parts.append("JOIN ") + if self.lateral: + parts.append("LATERAL ") + parts.append(str(self.right)) + if self.criteria: + parts.append(f" {self.criteria}") + return "".join(parts) + + +@dataclass(eq=False) +class FunctionTableExpression(TableExpression, Named, Operation): + """ + An uninitializable Type for FunctionTable for use as a + default where a FunctionTable is required but succeeds optional fields + """ + + args: List[Expression] = field(default_factory=list) + + +class FunctionTable(FunctionTableExpression): + """ + Represents a table-valued function used in a statement + """ + + def __str__(self) -> str: + alias = f" {self.alias}" if self.alias else "" + as_ = " AS " if self.as_ else "" + cols = ( + f" {', '.join(str(col) for col in self.column_list)}" + if self.column_list + else "" + ) + column_list_str = f"({cols})" if alias else str(cols) + args_str = f"({', '.join(str(col) for col in self.args)})" if self.args else "" + return f"{self.name}{args_str}{alias}{as_}{column_list_str}" + + def set_alias(self: TNode, alias: Name) -> TNode: + self.alias = alias + return self + + def _type(self, ctx: Optional[CompileContext] = None) -> List[NestedField]: + name = self.name.name.upper() + dj_func = table_function_registry[name] + arg_types = [] + for arg in self.args: + if ctx: + arg.compile(ctx) + arg_types.append(arg.type) + return dj_func.infer_type(*arg_types) + + def compile(self, ctx): + if self.is_compiled(): + return + self._is_compiled = True + types = self._type(ctx) + for type, col in zip_longest(types, self.column_list): + if self.column_list: + if (type is None) or (col is None): + ctx.exception.errors.append( + DJError( + code=ErrorCode.INVALID_SQL_QUERY, + message=( + "Found different number of columns than types" + f" in {self}." + ), + context=str(self), + ), + ) + break + else: + col = Column(type.name) + + col.add_type(type.type) + self._columns.append(col) + + +@dataclass(eq=False) +class LateralView(Node): + """ + Represents a lateral view expression + """ + + outer: bool = False + func: FunctionTableExpression = field(default_factory=FunctionTableExpression) + + def __str__(self) -> str: + parts = ["LATERAL VIEW"] + if self.outer: + parts.append(" OUTER") + parts.append(f" {self.func}") + return "".join(parts) + + +@dataclass(eq=False) +class Relation(Node): + """ + Represents a relation + """ + + primary: Expression + extensions: List[Join] = field(default_factory=list) + + def __str__(self) -> str: + if self.extensions: + extensions = " " + "\n".join([str(ext) for ext in self.extensions]) + else: + extensions = "" + return f"{self.primary}{extensions}" + + +@dataclass(eq=False) +class From(Node): + """ + Represents the FROM clause of a SELECT statement + """ + + relations: List[Relation] = field(default_factory=list) + + def __str__(self) -> str: + parts = ["FROM "] + parts += ",\n".join([str(r) for r in self.relations]) + + return "".join(parts) + + +@dataclass(eq=False) +class SetOp(Node): + """ + A set operation + """ + + kind: str = "" # Union, intersect, ... + right: Optional["SelectExpression"] = None + + def __str__(self) -> str: + return f"\n{self.kind}\n{self.right}" + + +@dataclass(eq=False) +class Cast(Expression): + """ + A cast to a specified type + """ + + data_type: ColumnType + expression: Expression + + def __str__(self) -> str: + return f"CAST({self.expression} AS {str(self.data_type).upper()})" + + @property + def type(self) -> ColumnType: + """ + Return the type of the expression + """ + return self.data_type + + +@dataclass(eq=False) +class SortItem(Node): + """ + Defines a sort item of an expression + """ + + expr: Expression + asc: str + nulls: str + + def __str__(self) -> str: + return f"{self.expr} {self.asc} {self.nulls}".strip() + + +@dataclass(eq=False) +class Organization(Node): + """ + Sets up organization for the query + """ + + order: List[SortItem] = field(default_factory=list) + sort: List[SortItem] = field(default_factory=list) + + def __str__(self) -> str: + ret = "" + ret += f"ORDER BY {', '.join(str(i) for i in self.order)}" if self.order else "" + if ret: + ret += "\n" + ret += f"SORT BY {', '.join(str(i) for i in self.sort)}" if self.sort else "" + return ret + + +@dataclass(eq=False) +class SelectExpression(Aliasable, Expression): + """ + An uninitializable Type for Select for use as a default where + a Select is required. + """ + + quantifier: str = "" # Distinct, All + projection: List[Union[Aliasable, Expression]] = field(default_factory=list) + from_: Optional[From] = None + group_by: List[Expression] = field(default_factory=list) + having: Optional[Expression] = None + where: Optional[Expression] = None + lateral_views: List[LateralView] = field(default_factory=list) + set_op: Optional[SetOp] = None + limit: Optional[Expression] = None + organization: Optional[Organization] = None + + def add_set_op(self, set_op: SetOp): + if self.set_op: + self.set_op.right.add_set_op(set_op) + else: + self.set_op = set_op + + def add_aliases_to_unnamed_columns(self) -> None: + """ + Add an alias to any unnamed columns in the projection (`col{n}`) + """ + projection = [] + for i, expression in enumerate(self.projection): + if not isinstance(expression, Aliasable): + name = f"col{i}" + projection.append(expression.set_alias(Name(name))) + else: + projection.append(expression) + self.projection = projection + + def __str__(self) -> str: + parts = ["SELECT "] + if self.quantifier: + parts.append(f"{self.quantifier}\n") + parts.append(",\n\t".join(str(exp) for exp in self.projection)) + if self.from_ is not None: + parts.extend(("\n", str(self.from_), "\n")) + for view in self.lateral_views: + parts.append(f"\n{view}") + if self.where is not None: + parts.extend(("WHERE ", str(self.where), "\n")) + if self.group_by: + parts.extend(("GROUP BY ", ", ".join(str(exp) for exp in self.group_by))) + if self.having is not None: + parts.extend(("HAVING ", str(self.having), "\n")) + select = " ".join(parts).strip() + if self.parenthesized: + select = f"({select})" + if self.set_op: + select += f"\n{self.set_op}" + + if self.organization: + select += f"\n{self.organization}" + if self.limit: + select += f"LIMIT {self.limit}" + + if self.alias: + as_ = " AS " if self.as_ else " " + return f"{select}{as_}{self.alias}" + return select + + +class Select(SelectExpression): + """ + A single select statement type + """ + + @property + def type(self) -> ColumnType: + if len(self.projection) != 1: + raise DJParseException( + "Can only infer type of a SELECT when it " + f"has a single expression in its projection. In {self}.", + ) + return self.projection[0].type + + def compile(self, ctx: CompileContext): + if not self.group_by and self.having: + ctx.exception.errors.append( + DJError( + code=ErrorCode.INVALID_SQL_QUERY, + message=( + "HAVING without a GROUP BY is not allowed. " + "Did you want to use a WHERE clause instead?" + ), + context=str(self), + ), + ) + + super().compile(ctx) + + +@dataclass(eq=False) +class Query(TableExpression, UnNamed): + """ + Overarching query type + """ + + select: SelectExpression = field(default_factory=SelectExpression) + ctes: List["Query"] = field(default_factory=list) + + def is_compiled(self) -> bool: + return not any( + self.filter(lambda node: node is not self and not node.is_compiled()), + ) + + def compile(self, ctx: CompileContext): + if self._is_compiled: + return + self.apply( + lambda node: node is not self + and not node.is_compiled() + and node.compile(ctx), + ) + for expr in self.select.projection: + self._columns += expr.columns + self._is_compiled = True + + def bake_ctes(self) -> "Query": + """ + Add ctes into the select and return the select + + Note: This destroys the structure of the query which cannot be undone + you may want to deepcopy it first + """ + + for cte in self.ctes: + for tbl in self.filter( + lambda node: isinstance(node, Table) + and node.identifier(False) == cte.alias_or_name.identifier(False), + ): + tbl.swap(cte) + self.ctes = [] + return self + + def __str__(self) -> str: + is_cte = self.parent is not None and self.parent_key == "ctes" + ctes = ",\n".join(str(cte) for cte in self.ctes) + if ctes: + ctes += "\n\n" + with_ = f"WITH\n{ctes}" if ctes else "" + + parts = [f"{with_}{self.select}\n"] + query = "".join(parts) + if self.parenthesized: + query = f"({query})" + if self.alias: + as_ = " AS " if self.as_ else " " + if is_cte: + query = f"{self.alias}{as_}{query}" + else: + query = f"{query}{as_}{self.alias}" + return query + + def set_alias(self: TNode, alias: "Name") -> TNode: + self.alias = alias + for col in self._columns: + if isinstance(col, Column): + col.table.alias = self.alias + return self + + def extract_dependencies( + self, + context: Optional[CompileContext] = None, + ) -> Tuple[Dict[NodeRevision, List[Table]], Dict[str, List[Table]]]: + """ + Find all dependencies in a compiled query + """ + + if not self.is_compiled(): + if not context: + raise DJException("Context not provided for query compilation!") + self.compile(context) + + deps: Dict[NodeRevision, List[Table]] = {} + danglers: Dict[str, List[Table]] = {} + for table in self.find_all(Table): + if node := table.dj_node: + deps[node] = deps.get(node, []) + deps[node].append(table) + else: + name = table.identifier(quotes=False) + danglers[name] = danglers.get(name, []) + danglers[name].append(table) + + return deps, danglers + + @property + def type(self) -> ColumnType: + return self.select.type + + def build( # pylint: disable=R0913,C0415 + self, + session: Session, + memoized_queries: Dict[int, "Query"], + build_criteria: Optional[BuildCriteria] = None, + ): + """ + Transforms a query ast by replacing dj node references with their asts + """ + from datajunction_server.construction.build import _build_select_ast + + self.bake_ctes() # pylint: disable=W0212 + _build_select_ast(session, self.select, memoized_queries, build_criteria) + self.select.add_aliases_to_unnamed_columns() + + # Make the generated query deterministic + self.select.projection = sorted( + self.select.projection, + key=lambda x: str(x.alias_or_name), + )[:] + + +################################### +###SERIALIZATION/DESERIALIZATION### +################################### +def get_node_key(node: Node) -> int: + """ + Returns the unique identifier of a node. + """ + return id(node) + + +def serialize_value( + value: Any, + serialization: Dict[int, Tuple[str, Dict[str, Any]]], + visited_nodes: Set[int], +) -> Any: + """ + Serializes a value to a dictionary representation. + """ + if isinstance(value, Node): + node_key = get_node_key(value) + if node_key in visited_nodes: + return {"kind": "node", "value": node_key} + visited_nodes.add(node_key) + _serialize_ast(value, serialization, visited_nodes) + return {"kind": "node", "value": node_key} + if isinstance(value, ColumnType): + return {"kind": "type", "value": str(value)} + if isinstance(value, DJEnum): + return {"kind": "primitive", "value": value.value} + if type(value) in PRIMITIVES: + return {"kind": "primitive", "value": value} + if isinstance(value, list): + return { + "kind": "list", + "value": [ + serialize_value(item, serialization, visited_nodes) for item in value + ], + } + if isinstance(value, tuple): + return { + "kind": "tuple", + "value": [ + serialize_value(item, serialization, visited_nodes) for item in value + ], + } + if isinstance(value, set): + return { + "kind": "set", + "value": [ + serialize_value(item, serialization, visited_nodes) for item in value + ], + } + + +def _serialize_ast( + node: Node, + serialization: Dict[int, Tuple[str, Dict[str, Any]]], + visited_nodes: Set[int], +): + """ + Recursively serializes an AST node and its children. + """ + node_key = get_node_key(node) + if node_key in serialization: + return + cls_name = type(node).__name__ + + data = {} + for key, value in node.__dict__.items(): + data[key] = serialize_value(value, serialization, visited_nodes) + serialization[node_key] = (cls_name, data) + + +def serialize_ast(node: Node) -> Dict[int, Tuple[str, Dict[str, Any]]]: + """ + Serializes an AST node and returns its serialization. + """ + ret = {} + visited_nodes = set() + _serialize_ast(node, ret, visited_nodes) + return ret + + +def deserialize_value( + parent_id: int, + parent_key: str, + value: Any, + serialization: Dict[int, Tuple[str, Dict[str, Any]]], + visited: Set[int], + lazies: List["LazyNode"], +) -> Any: + """ + Deserializes a value from its dictionary representation. + """ + if not value: + return + if value["kind"] == "node": + node_key = value["value"] + return ( + _deserialize_ast( + parent_id, + parent_key, + node_key, + serialization, + visited, + lazies, + ) + or serialization[node_key] + ) + elif value["kind"] == "type": + from datajunction_server.sql.parsing.backends.antlr4 import parse + + return parse(f"select CAST(x as {value['value']})").select.projection[0].type # type: ignore + elif value["kind"] == "primitive": + return value["value"] + elif value["kind"] == "list": + return [ + deserialize_value( + parent_id, + parent_key, + item, + serialization, + visited, + lazies, + ) + for item in value["value"] + ] + elif value["kind"] == "tuple": + return tuple( + deserialize_value( + parent_id, + parent_key, + item, + serialization, + visited, + lazies, + ) + for item in value["value"] + ) + elif value["kind"] == "set": + return { + deserialize_value( + parent_id, + parent_key, + item, + serialization, + visited, + lazies, + ) + for item in value["value"] + } + raise TypeError(f"Cannot deserialize value `{value}`.") + + +@dataclass +class LazyNode(Node): + """ + Type used during deserialization of an AST + in place of nodes that have yet to finish + deserializing. + """ + + parent_id: Optional[int] = None + parent_key: Optional[str] = None + key: Optional[int] = None + refs: Optional[Dict[int, Tuple[str, Dict[str, Any]]]] = None + + def finalize(self) -> Node: + """ + Replaces the lazy node with the fully deserialized node. + """ + node = self.refs[self.key] # type: ignore + self.parent = self.refs[self.parent_id] # type: ignore + node = node.copy() + self.swap(node) + return node + + def __str__(self): + raise NotImplementedError() + + +def _deserialize_ast( + parent_id: Optional[int], + parent_key: Optional[str], + node_id: int, + serialization: Dict[int, Tuple[str, Dict[str, Any]]], + visited: Set[int], + lazies: List[LazyNode], +) -> Node: + """ + Recursively deserializes an AST node and its children. + """ + value = serialization[node_id] + if isinstance(value, Node): # node already deserialized + return value + elif node_id in visited: # circular references + # create a lazy node to be swapped once deserialization is complete + lazy = LazyNode(parent_id, parent_key, node_id, serialization) + lazies.append(lazy) + return lazy + else: # node not deserialized yet + cls_name, data = value + visited.add(node_id) + cls = globals().get(cls_name) + # get the fields we can feed the class init + init_fields = {field.name for field in fields(cls) if field.init == True} + attrs = [] + kwargs = {} + for key, value in data.items(): + if key in init_fields: + deserialized_value = deserialize_value( + node_id, + key, + value, + serialization, + visited, + lazies, + ) + kwargs[key] = deserialized_value + else: + attrs.append(key) + ret = cls(**kwargs) # type: ignore + # set the rest of the attributes from data + for key in attrs: + deserialized_value = deserialize_value( + node_id, + key, + data[key], + serialization, + visited, + lazies, + ) + setattr(ret, key, deserialized_value) + + serialization[node_id] = ret + + return ret + + +def deserialize_ast( + node_id: int, + serialization: Dict[int, Tuple[str, Dict[str, Any]]], +) -> Node: + """ + Deserializes an AST from its serialization and returns the root node. + """ + lazies = [] + ret = _deserialize_ast(None, None, node_id, serialization, set(), lazies) + for lazy in lazies: + lazy.finalize() + return ret diff --git a/datajunction-server/datajunction_server/sql/parsing/backends/__init__.py b/datajunction-server/datajunction_server/sql/parsing/backends/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/datajunction-server/datajunction_server/sql/parsing/backends/antlr4.py b/datajunction-server/datajunction_server/sql/parsing/backends/antlr4.py new file mode 100644 index 000000000..7650d61a9 --- /dev/null +++ b/datajunction-server/datajunction_server/sql/parsing/backends/antlr4.py @@ -0,0 +1,1136 @@ +# pylint: skip-file +# mypy: ignore-errors +import inspect +import logging +from typing import TYPE_CHECKING, List, Optional, Tuple, Union, cast + +import antlr4 +from antlr4 import InputStream, RecognitionException +from antlr4.error.ErrorListener import ErrorListener +from antlr4.error.Errors import ParseCancellationException +from antlr4.error.ErrorStrategy import BailErrorStrategy + +import datajunction_server.sql.parsing.types as ct +from datajunction_server.sql.parsing import ast +from datajunction_server.sql.parsing.ast import UnaryOpKind +from datajunction_server.sql.parsing.backends.exceptions import DJParseException +from datajunction_server.sql.parsing.backends.grammar.generated.SqlBaseLexer import ( + SqlBaseLexer, +) +from datajunction_server.sql.parsing.backends.grammar.generated.SqlBaseParser import ( + SqlBaseParser, +) +from datajunction_server.sql.parsing.backends.grammar.generated.SqlBaseParser import ( + SqlBaseParser as sbp, +) + +if TYPE_CHECKING: + from datajunction_server.sql.parsing.types import ColumnType + +logger = logging.getLogger(__name__) + + +class RemoveIdentifierBackticks(antlr4.ParseTreeListener): + @staticmethod + def exitQuotedIdentifier(ctx): # pylint: disable=invalid-name,unused-argument + def identity(token): + return token + + return identity + + @staticmethod + def enterNonReserved(ctx): # pylint: disable=invalid-name,unused-argument + def add_backtick(token): + return "`{0}`".format(token) + + return add_backtick + + +class ParseErrorListener(ErrorListener): + def syntaxError( + self, + recognizer, + offendingSymbol, + line, + column, + msg, + e, + ): # pylint: disable=invalid-name,no-self-use,too-many-arguments + raise SqlSyntaxError(f"Parse error {line}:{column}:", msg) + + +class UpperCaseCharStream: + """ + Make SQL token detection case insensitive and allow identifier without + backticks to be seen as e.g. column names + """ + + def __init__(self, wrapped): + self.wrapped = wrapped + + def getText(self, interval, *args): # pylint: disable=invalid-name + if args or (self.size() > 0 and (interval.b - interval.a >= 0)): + return self.wrapped.getText(interval, *args) + return "" + + def LA(self, i: int): # pylint: disable=invalid-name + token = self.wrapped.LA(i) + if token in (0, -1): + return token + return ord(chr(token).upper()) + + def __getattr__(self, item): + return getattr(self.wrapped, item) + + +class ExplicitBailErrorStrategy(BailErrorStrategy): + """ + Bail Error Strategy throws a ParseCancellationException, + This strategy simply throw a more explicit exception + """ + + def recover(self, recognizer, e: RecognitionException): + try: + super(ExplicitBailErrorStrategy, self).recover(recognizer, e) + except ParseCancellationException: + raise SqlParsingError from e + + +class EarlyBailSqlLexer(SqlBaseLexer): + def recover(self, recognition_exc: RecognitionException): + raise SqlLexicalError from recognition_exc + + +def build_parser(stream, strict_mode=False, early_bail=True): + if not strict_mode: + stream = UpperCaseCharStream(stream) + if early_bail: + lexer = EarlyBailSqlLexer(stream) + else: + lexer = SqlBaseLexer(stream) + lexer.removeErrorListeners() + lexer.addErrorListener(ParseErrorListener()) + token_stream = antlr4.CommonTokenStream(lexer) + parser = SqlBaseParser(token_stream) + parser.addParseListener(RemoveIdentifierBackticks()) + parser.removeErrorListeners() + parser.addErrorListener(ParseErrorListener()) + if early_bail: + parser._errHandler = ExplicitBailErrorStrategy() + return parser + + +class SqlParsingError(Exception): + pass + + +class SqlLexicalError(SqlParsingError): + pass + + +class SqlSyntaxError(SqlParsingError): + pass + + +def string_to_ast(string, rule, *, strict_mode=False, debug=False, early_bail=False): + parser = build_string_parser(string, strict_mode, early_bail) + tree = getattr(parser, rule)() + if debug: + print_tree(tree, printer=logger.warning) + return tree + + +def build_string_parser(string, strict_mode=False, early_bail=True): + string_as_stream = InputStream(string) + parser = build_parser(string_as_stream, strict_mode, early_bail) + return parser + + +def parse_sql(string, rule, converter=None, debug=False): + tree = string_to_ast(string, rule, debug=debug) + return converter(tree) if converter else tree + + +def parse_statement(string, converter=None, debug=False): + return parse_sql(string, "singleStatement", converter, debug) + + +def print_tree(tree, printer=print): + for line in tree_to_strings(tree, indent=0): + printer(line) + + +def tree_to_strings(tree, indent=0): + symbol = ("[" + tree.symbol.text + "]") if hasattr(tree, "symbol") else "" + node_as_string = type(tree).__name__ + symbol + result = ["|" + "-" * indent + node_as_string] + if hasattr(tree, "children") and tree.children: + for child in tree.children: + result += tree_to_strings(child, indent + 1) + return result + + +def parse_rule(sql: str, rule: str) -> Union[ast.Node, "ColumnType"]: + """ + Parse a string into a DJ ast using the ANTLR4 backend. + """ + antlr_tree = parse_sql(sql, rule) + ast_tree = visit(antlr_tree) + return ast_tree + + +def parse(sql: Optional[str]) -> ast.Query: + """ + Parse a string sql query into a DJ ast Query + """ + if not sql: + raise DJParseException("Empty query provided!") + return cast(ast.Query, parse_rule(sql, "singleStatement")) + + +TERMINAL_NODE = antlr4.tree.Tree.TerminalNodeImpl + + +class Visitor: + def __init__(self): + self.registry = {} + + def register(self, func): + params = inspect.signature(func).parameters + type_ = params[list(params.keys())[0]].annotation + if type_ == inspect.Parameter.empty: + raise ValueError( + "No type annotation found for the first parameter of the visitor.", + ) + if type_ in self.registry: + raise ValueError( + f"A visitor is already registered for type {type_.__name__}.", + ) + self.registry[type_] = func + return func + + def __call__(self, ctx): + if type(ctx) == TERMINAL_NODE: + return None + func = self.registry.get(type(ctx), None) + if func is None: + line, col = ctx.start.line, ctx.start.column + raise TypeError( + f"{line}:{col} No visitor registered for type {type(ctx).__name__}", + ) + result = func(ctx) + + if result is None: + line, col = ctx.start.line, ctx.start.column + raise DJParseException(f"{line}:{col} Could not parse {ctx.getText()}") + if ( + hasattr(result, "parenthesized") + and result.parenthesized is None + and hasattr(ctx, "LEFT_PAREN") + ): + if text := ctx.getText(): + text = text.strip() + if (text[0] == "(") and (text[-1] == ")"): + result.parenthesized = True + if ( + hasattr(ctx, "AS") + and ctx.AS() + and hasattr(result, "as_") + and result.as_ is None + ): + result = result.set_as(True) + return result + + +visit = Visitor() + + +@visit.register +def _(ctx: list, nones=False): + return list( + filter( + lambda child: child is not None if nones is False else True, + map(visit, ctx), + ), + ) + + +@visit.register +def _(ctx: sbp.SingleStatementContext): + return visit(ctx.statement()) + + +@visit.register +def _(ctx: sbp.StatementDefaultContext): + return visit(ctx.query()) + + +@visit.register +def _(ctx: sbp.QueryContext): + ctes = [] + if ctes_ctx := ctx.ctes(): + ctes = visit(ctes_ctx) + limit, organization = visit(ctx.queryOrganization()) + select = visit(ctx.queryTerm()) + select.limit = limit + select.organization = organization + return ast.Query(ctes=ctes, select=select) + + +@visit.register +def _(ctx: sbp.QueryOrganizationContext): + order = visit(ctx.order) + sort = visit(ctx.sort) + org = ast.Organization(order, sort) + limit = None + if ctx.limit: + limit = visit(ctx.limit) + return limit, org + + +@visit.register +def _(ctx: sbp.SortItemContext): + expr = visit(ctx.expression()) + order = "" + if ordering := ctx.ordering: + order = ordering.text.upper() + nulls = "" + if null_order := ctx.nullOrder: + nulls = "NULLS " + null_order.text + return ast.SortItem(expr, order, nulls) + + +@visit.register +def _(ctx: sbp.ExpressionContext): + return visit(ctx.booleanExpression()) + + +@visit.register +def _(ctx: sbp.BooleanLiteralContext): + boolean_value_mapping = {"true": True, "false": False} + boolean_value = ctx.booleanValue().getText().lower() + if boolean_value in boolean_value_mapping: + return ast.Boolean(boolean_value_mapping[boolean_value]) + raise DJParseException(f"Invalid boolean value {boolean_value}!") + + +@visit.register +def _(ctx: sbp.BooleanValueContext): + return ast.Boolean(visit(ctx.getText())) + + +@visit.register +def _(ctx: sbp.PredicatedContext): + if value_expr := ctx.valueExpression(): + if not ctx.predicate(): + return visit(value_expr) + if predicate_ctx := ctx.predicate(): + return visit(predicate_ctx) + + +@visit.register +def _(ctx: sbp.PredicateContext) -> ast.Predicate: + negated = True if ctx.NOT() else False + any = True if ctx.ANY() else False + all = True if ctx.ALL() else False + some = True if ctx.SOME() else False + expr = visit(ctx.parentCtx.valueExpression()) + if ctx.BETWEEN(): + low = visit(ctx.lower) + high = visit(ctx.upper) + return ast.Between(negated, expr, low, high) + if ctx.DISTINCT(): + right = visit(ctx.right) + return ast.IsDistinctFrom(negated, expr, right) + if ctx.LIKE(): + pattern = visit(ctx.pattern) + return ast.Like(negated, expr, all or any or some or "", pattern, ctx.ESCAPE()) + if ctx.ILIKE(): + pattern = visit(ctx.pattern) + return ast.Like( + negated, + expr, + all or any or some or "", + pattern, + ctx.ESCAPE(), + case_sensitive=False, + ) + if ctx.IN(): + if source_ctx := ctx.query(): + source = visit(source_ctx.queryTerm()) + source.parenthesized = True + if source_ctx := ctx.expression(): + source = visit(source_ctx) + return ast.In(negated, expr, source) + if ctx.IS(): + if ctx.NULL(): + return ast.IsNull(negated, expr) + if ctx.TRUE(): + return ast.IsBoolean(negated, expr, "TRUE") + if ctx.FALSE(): + return ast.IsBoolean(negated, expr, "FALSE") + if ctx.UNKNOWN(): + return ast.IsBoolean(negated, expr, "UNKNOWN") + if ctx.RLIKE(): + pattern = visit(ctx.pattern) + return ast.Rlike(negated, expr, pattern) + return + + +@visit.register +def _(ctx: sbp.SubscriptContext): + return ast.Subscript( + expr=visit(ctx.primaryExpression()), + index=visit(ctx.valueExpression()), + ) + + +@visit.register +def _(ctx: sbp.ValueExpressionContext): + if primary := ctx.primaryExpression(): + return visit(primary) + + +@visit.register +def _(ctx: sbp.ValueExpressionDefaultContext): + return visit(ctx.primaryExpression()) + + +@visit.register +def _(ctx: sbp.ArithmeticBinaryContext): + return ast.BinaryOp( + ast.BinaryOpKind(ctx.operator.text.upper()), + visit(ctx.left), + visit(ctx.right), + ) + + +@visit.register +def _(ctx: sbp.ColumnReferenceContext): + return ast.Column(visit(ctx.identifier())) + + +@visit.register +def _(ctx: sbp.QueryTermDefaultContext): + return visit(ctx.queryPrimary()) + + +@visit.register +def _(ctx: sbp.QueryPrimaryDefaultContext): + return visit(ctx.querySpecification()) + + +@visit.register +def _(ctx: sbp.QueryTermContext): + if primary_query := ctx.queryPrimary(): + return visit(primary_query) + + +@visit.register +def _(ctx: sbp.QueryPrimaryContext): + return visit(ctx.querySpecification()) + + +@visit.register +def _(ctx: sbp.RegularQuerySpecificationContext): + quantifier, projection = visit(ctx.selectClause()) + from_ = visit(ctx.fromClause()) if ctx.fromClause() else None + laterals = visit(ctx.lateralView()) + group_by = visit(ctx.aggregationClause()) if ctx.aggregationClause() else [] + where = None + if where_clause := ctx.whereClause(): + where = visit(where_clause) + having = None + if having_clause := ctx.havingClause(): + having = visit(having_clause) + select = ast.Select( + quantifier=quantifier, + projection=projection, + from_=from_, + lateral_views=laterals, + where=where, + group_by=group_by, + having=having, + ) + if from_ and from_.laterals: + select.lateral_views += from_.laterals + del from_.laterals + return select + + +@visit.register +def _(ctx: sbp.HavingClauseContext): + return visit(ctx.booleanExpression()) + + +@visit.register +def _(ctx: sbp.AggregationClauseContext): + return visit(ctx.groupByClause()) + + +@visit.register +def _(ctx: sbp.GroupByClauseContext): + if grouping_analytics := ctx.groupingAnalytics(): + return visit(grouping_analytics) + if expression := ctx.expression(): + return visit(expression) + + +@visit.register +def _(ctx: sbp.GroupingAnalyticsContext): + grouping_set = visit(ctx.groupingSet()) + if ctx.ROLLUP(): + return ast.Function(ast.Name(name="ROLLUP"), args=grouping_set) + if ctx.CUBE(): + return ast.Function(ast.Name(name="CUBE"), args=grouping_set) + return grouping_set + + +@visit.register +def _(ctx: sbp.GroupingSetContext): + return ast.Column(ctx.getText()) + + +@visit.register +def _(ctx: sbp.SelectClauseContext): + quantifier = "" + if quant := ctx.setQuantifier(): + quantifier = visit(quant) + projection = visit(ctx.namedExpressionSeq()) + return quantifier, projection + + +@visit.register +def _(ctx: sbp.SetQuantifierContext): + if ctx.DISTINCT(): + return "DISTINCT" + if ctx.ALL(): + return "ALL" + return "" + + +@visit.register +def _(ctx: sbp.NamedExpressionSeqContext): + return visit(ctx.namedExpression()) + + +@visit.register +def _(ctx: sbp.NamedExpressionContext): + expr = visit(ctx.expression()) + if alias := ctx.name: + return expr.set_alias(visit(alias)) + + if col_names := ctx.identifierList(): + if not isinstance(expr, ast.TableExpression): + raise SqlSyntaxError( + f"{ctx.start.line}:{ctx.start.column} Cannot use an identifier" + "list as an alias on a non-Table Expression.", + ) + expr.column_list = [ast.Column(name) for name in visit(col_names)] + if ctx.AS(): + expr.set_as(True) + return expr + + +@visit.register +def _(ctx: sbp.ErrorCapturingIdentifierContext): + name = visit(ctx.identifier()) + if extra := visit(ctx.errorCapturingIdentifierExtra()): + name.name += extra + name.quote_style = '"' + return name + + +@visit.register +def _(ctx: sbp.ErrorIdentContext): + return ctx.getText() + + +@visit.register +def _(ctx: sbp.RealIdentContext): + return "" + + +@visit.register +def _(ctx: sbp.FirstContext): + return ast.Function(ast.Name("FIRST"), args=[visit(ctx.expression())]) + + +@visit.register +def _(ctx: sbp.IdentifierContext): + return visit(ctx.strictIdentifier()) + + +@visit.register +def _(ctx: sbp.UnquotedIdentifierContext): + return ast.Name(ctx.getText()) + + +@visit.register +def _(ctx: sbp.ConstantDefaultContext): + return visit(ctx.constant()) + + +@visit.register +def _(ctx: sbp.NumericLiteralContext): + return ast.Number(ctx.number().getText()) + + +@visit.register +def _(ctx: sbp.StringLitContext): + if string := ctx.STRING(): + return string.getSymbol().text.strip("'") + return ctx.DOUBLEQUOTED_STRING().getSymbol().text.strip('"') + + +@visit.register +def _(ctx: sbp.DereferenceContext): + base = visit(ctx.base) + field = visit(ctx.fieldName) + field.namespace = base.name + base.name = field + return base + + +@visit.register +def _(ctx: sbp.FunctionCallContext): + name = visit(ctx.functionName()) + quantifier = visit(ctx.setQuantifier()) if ctx.setQuantifier() else "" + over = visit(ctx.windowSpec()) if ctx.windowSpec() else None + args = visit(ctx.argument) + return ast.Function(name, args, quantifier=quantifier, over=over) + + +@visit.register +def _(ctx: sbp.WindowDefContext): + partition_by = visit(ctx.partition) + order_by = visit(ctx.sortItem()) + window_frame = visit(ctx.windowFrame()) if ctx.windowFrame() else None + return ast.Over( + partition_by=partition_by, + order_by=order_by, + window_frame=window_frame, + ) + + +@visit.register +def _(ctx: sbp.WindowFrameContext): + start = visit(ctx.start) + end = visit(ctx.end) if ctx.end else None + return ast.Frame(ctx.frameType.text, start=start, end=end) + + +@visit.register +def _(ctx: sbp.FrameBoundContext): + return ast.FrameBound(start=ctx.start.text, stop=ctx.stop.text) + + +@visit.register +def _(ctx: sbp.FunctionNameContext): + if qual_name := ctx.qualifiedName(): + return visit(qual_name) + return ast.Name(ctx.getText()) + + +@visit.register +def _(ctx: sbp.QualifiedNameContext): + names = visit(ctx.children) + for i in range(len(names) - 1, 0, -1): + names[i].namespace = names[i - 1] + return names[-1] + + +@visit.register +def _(ctx: sbp.StarContext): + namespace = None + if qual_name := ctx.qualifiedName(): + namespace = visit(qual_name) + star = ast.Wildcard() + star.name.namespace = namespace + return star + + +@visit.register +def _(ctx: sbp.TableNameContext): + if ctx.temporalClause(): + return + name = visit(ctx.multipartIdentifier()) + table_alias = ctx.tableAlias() + alias, cols = visit(table_alias) + table = ast.Table(name, column_list=cols) + if alias: + table = table.set_alias(ast.Name(alias)) + if table_alias.AS(): + table = table.set_as(True) + return table + + +@visit.register +def _(ctx: sbp.MultipartIdentifierContext): + names = visit(ctx.children) + for i in range(len(names) - 1, 0, -1): + names[i].namespace = names[i - 1] + return names[-1] + + +@visit.register +def _(ctx: sbp.QuotedIdentifierAlternativeContext): + return visit(ctx.quotedIdentifier()) + + +@visit.register +def _(ctx: sbp.QuotedIdentifierContext): + if ident := ctx.BACKQUOTED_IDENTIFIER(): + return ast.Name(ident.getText()[1:-1], quote_style="`") + return ast.Name(ctx.DOUBLEQUOTED_STRING().getText()[1:-1], quote_style='"') + + +@visit.register +def _(ctx: sbp.LateralViewContext): + outer = bool(ctx.OUTER()) + func_name = visit(ctx.qualifiedName()) + func_args = visit(ctx.expression()) + table_name = visit(ctx.tblName) + function_table_name = table_name if table_name != ast.Name(name="AS") else None + func = ast.FunctionTable(func_name, args=func_args, alias=function_table_name) + func.set_alias(function_table_name) + if function_table_name: + func.column_list = [ + ast.Column(name=ast.Name(name=name.name, namespace=function_table_name)) + for name in visit(ctx.colName) + ] + else: + func.column_list = [ast.Column(name=name) for name in visit(ctx.colName)] + if ctx.AS() or table_name == ast.Name(name="AS"): + func.set_as(True) + return ast.LateralView(outer, func) + + +@visit.register +def _(ctx: sbp.RelationContext): + primary_relation = visit(ctx.relationPrimary()) + extensions = visit(ctx.relationExtension()) if ctx.relationExtension() else [] + return ast.Relation(primary_relation, extensions) + + +@visit.register +def _(ctx: sbp.RelationExtensionContext): + if join_rel := ctx.joinRelation(): + return visit(join_rel) + + +@visit.register +def _(ctx: sbp.JoinTypeContext): + anti = f"{ctx.ANTI()} " if ctx.ANTI() else "" + cross = f"{ctx.CROSS()} " if ctx.CROSS() else "" + full = f"{ctx.FULL()} " if ctx.FULL() else "" + inner = f"{ctx.INNER()} " if ctx.INNER() else "" + outer = f"{ctx.OUTER()} " if ctx.OUTER() else "" + semi = f"{ctx.SEMI()} " if ctx.SEMI() else "" + left = f"{ctx.LEFT()} " if ctx.LEFT() else "" + right = f"{ctx.RIGHT()} " if ctx.RIGHT() else "" + return f"{left}{right}{cross}{full}{semi}{outer}{inner}{anti}" + + +@visit.register +def _(ctx: sbp.JoinRelationContext): + kind = visit(ctx.joinType()) + lateral = bool(ctx.LATERAL()) + natural = bool(ctx.NATURAL()) + criteria = None + right = visit(ctx.right) + if join_criteria := ctx.joinCriteria(): + criteria = visit(join_criteria) + return ast.Join(kind, right, criteria=criteria, lateral=lateral, natural=natural) + + +@visit.register +def _(ctx: sbp.TableValuedFunctionContext): + return visit(ctx.functionTable()) + + +@visit.register +def _(ctx: sbp.FunctionTableContext): + name = visit(ctx.funcName) + args = visit(ctx.expression()) + alias, cols = visit(ctx.tableAlias()) + func_table = ast.FunctionTable( + name, + args=args, + column_list=[ast.Column(name) for name in cols], + alias=alias, + ) + if not cols: + func_table.column_list = alias + return func_table + + +@visit.register +def _(ctx: sbp.TableAliasContext): + if ident := ctx.strictIdentifier(): + identifier_list = visit(ctx.identifierList()) if ctx.identifierList() else [] + return visit(ident), identifier_list + return None, [] + + +@visit.register +def _(ctx: sbp.IdentifierListContext): + return visit(ctx.identifierSeq()) + + +@visit.register +def _(ctx: sbp.IdentifierSeqContext): + return visit(ctx.ident) + + +@visit.register +def _(ctx: sbp.FromClauseContext): + relations = visit(ctx.relation()) + laterals = visit(ctx.lateralView()) + if ctx.pivotClause() or ctx.unpivotClause(): + return + from_ = ast.From(relations) + from_.laterals = laterals + return from_ + + +@visit.register +def _(ctx: sbp.JoinCriteriaContext): + if expr := ctx.booleanExpression(): + return ast.JoinCriteria(on=visit(expr)) + return ast.JoinCriteria(using=(visit(ctx.identifierList()))) + + +@visit.register +def _(ctx: sbp.ComparisonContext): + left, right = visit(ctx.left), visit(ctx.right) + op = visit(ctx.comparisonOperator()) + return ast.BinaryOp(ast.BinaryOpKind(op.upper()), left, right) + + +@visit.register +def _(ctx: sbp.ComparisonOperatorContext): + return ctx.getText() + + +@visit.register +def _(ctx: sbp.WhereClauseContext): + return visit(ctx.booleanExpression()) + + +@visit.register +def _(ctx: sbp.StringLiteralContext): + return ast.String(ctx.getText()) + + +@visit.register +def _(ctx: sbp.CtesContext) -> List[ast.Select]: + names = {} + ctes = [] + for namedQuery in ctx.namedQuery(): + if namedQuery.name in names: + raise SqlSyntaxError(f"Duplicate CTE definition names: {namedQuery.name}") + query = visit(namedQuery.query()) + query = query.set_alias(visit(namedQuery.name)) + if namedQuery.AS(): + query = query.set_as(True) + query.parenthesized = True + ctes.append(query) + return ctes + + +@visit.register +def _(ctx: sbp.NamedQueryContext) -> ast.Select: + return visit(ctx.query()) + + +@visit.register +def _(ctx: sbp.LogicalBinaryContext) -> ast.BinaryOp: + return ast.BinaryOp( + ast.BinaryOpKind(ctx.operator.text.upper()), + visit(ctx.left), + visit(ctx.right), + ) + + +@visit.register +def _(ctx: sbp.SubqueryExpressionContext): + return visit(ctx.query()) + + +@visit.register +def _(ctx: sbp.SetOperationContext): + operator = ctx.operator.text + quantifier = f" {visit(ctx.setQuantifier())}" if ctx.setQuantifier() else "" + left = visit(ctx.left) + left.add_set_op( + ast.SetOp( + kind=f"{operator}{quantifier}", + right=visit(ctx.right), + ), + ) + return left + + +@visit.register +def _(ctx: sbp.AliasedQueryContext) -> ast.Select: + query = visit(ctx.query()) + query.parenthesized = True + table_alias = ctx.tableAlias() + ident, _ = visit(table_alias) + if ident: + query = query.set_alias(ident) + if table_alias.AS(): + query = query.set_as(True) + return query + + +@visit.register +def _(ctx: sbp.SimpleCaseContext) -> ast.Case: + expr = visit(ctx.value) + conditions = [] + results = [] + for when in ctx.whenClause(): + condition, result = visit(when) + conditions.append(condition) + results.append(result) + return ast.Case( + expr, + conditions=conditions, + else_result=visit(ctx.elseExpression) if ctx.elseExpression else None, + results=results, + ) + + +@visit.register +def _(ctx: sbp.SearchedCaseContext) -> ast.Case: + conditions = [] + results = [] + for when in ctx.whenClause(): + condition, result = visit(when) + conditions.append(condition) + results.append(result) + return ast.Case( + None, + conditions=conditions, + else_result=visit(ctx.elseExpression) if ctx.elseExpression else None, + results=results, + ) + + +@visit.register +def _(ctx: sbp.WhenClauseContext) -> Tuple[ast.Expression, ast.Expression]: + condition, result = visit(ctx.condition), visit(ctx.result) + return condition, result + + +@visit.register +def _(ctx: sbp.ParenthesizedExpressionContext) -> ast.Expression: + expr = visit(ctx.expression()) + expr.parenthesized = True + return expr + + +@visit.register +def _(ctx: sbp.NullLiteralContext) -> ast.Null: + return ast.Null() + + +@visit.register +def _(ctx: sbp.CastContext) -> ast.Cast: + data_type = visit(ctx.dataType()) + expression = visit(ctx.expression()) + return ast.Cast(data_type=data_type, expression=expression) + + +@visit.register +def _(ctx: sbp.ExistsContext) -> ast.UnaryOp: + expr = visit(ctx.query().queryTerm()) + expr.parenthesized = True + return ast.UnaryOp(op=UnaryOpKind.Exists, expr=expr) + + +@visit.register +def _(ctx: sbp.LogicalNotContext) -> ast.UnaryOp: + return ast.UnaryOp(op=UnaryOpKind.Not, expr=visit(ctx.booleanExpression())) + + +@visit.register +def _(ctx: sbp.IntervalLiteralContext) -> ast.Interval: + return visit(ctx.interval()) + + +@visit.register +def _(ctx: sbp.SubqueryContext): + return visit(ctx.query().queryTerm()) + + +@visit.register +def _(ctx: sbp.StructContext) -> ast.Function: + return ast.Function( + ast.Name("struct"), + args=visit(ctx.argument), + ) + + +@visit.register +def _(ctx: sbp.IntervalContext) -> ast.Interval: + return visit(ctx.children[1]) + + +@visit.register +def _(ctx: sbp.ErrorCapturingMultiUnitsIntervalContext) -> ast.Interval: + from_ = visit(ctx.body) + to = None + if utu := ctx.unitToUnitInterval(): + to = visit(utu) + interval = ast.Interval(from_ + to.from_, to.to) + else: + interval = ast.Interval(from_) + return interval + + +@visit.register +def _(ctx: sbp.UnitToUnitIntervalContext) -> ast.Interval: + value = visit(ctx.value) + from_ = visit(ctx.from_) + to = visit(ctx.to) + return ast.Interval([ast.IntervalUnit(from_, value)], ast.IntervalUnit(to)) + + +@visit.register +def _(ctx: sbp.MultiUnitsIntervalContext) -> List[ast.IntervalUnit]: + units = [] + for pair_i in range(0, len(ctx.children), 2): + value, unit = ctx.children[pair_i : pair_i + 2] + value = visit(value) + unit = visit(unit) + units.append(ast.IntervalUnit(unit, value)) + return units + + +@visit.register +def _(ctx: sbp.ErrorCapturingUnitToUnitIntervalContext) -> ast.Interval: + if ctx.error1 or ctx.error2: + raise SqlSyntaxError( + f"{ctx.start.line}:{ctx.start.column} Error capturing unit-to-unit interval.", + ) + return visit(ctx.body) + + +@visit.register +def _(ctx: sbp.IntervalValueContext) -> ast.Number: + if stringlit_ctx := ctx.stringLit(): + return ast.Number(visit(stringlit_ctx)) + return ast.Number(ctx.getText()) + + +@visit.register +def _(ctx: sbp.UnitInMultiUnitsContext) -> str: + return ctx.getText().upper().rstrip("S") + + +@visit.register +def _(ctx: sbp.UnitInUnitToUnitContext) -> str: + return ctx.getText().upper() + + +@visit.register +def _(ctx: sbp.TrimContext) -> ast.Function: + both = "BOTH " if ctx.BOTH() else "" + leading = "LEADING " if ctx.LEADING() else "" + trailing = "TRAILING " if ctx.TRAILING() else "" + from_ = "FROM " if ctx.FROM() else "" + return ast.Function( + ast.Name("TRIM"), + [visit(ctx.srcStr)], + f"{both or leading or trailing}{from_}", + ) + + +@visit.register +def _(ctx: sbp.LambdaContext) -> ast.Lambda: + identifier = visit(ctx.identifier()) + expr = visit(ctx.expression()) + lambda_expr = ast.Lambda(identifiers=identifier, expr=expr) + lambda_expr._type = ct.LambdaType + return lambda_expr + + +@visit.register +def _(ctx: sbp.PrimitiveDataTypeContext) -> ast.Value: + column_type = ctx.getText().strip() + decimal_match = ct.DECIMAL_REGEX.match(column_type) + if decimal_match: + precision = int(decimal_match.group("precision")) + scale = int(decimal_match.group("scale")) + return ct.DecimalType(precision, scale) + + fixed_match = ct.FIXED_PARSER.match(column_type) + if fixed_match: + length = int(fixed_match.group("length")) + return ct.FixedType(length) + + varchar_match = ct.VARCHAR_PARSER.match(column_type) + if varchar_match: + length = varchar_match.group("length") + return ct.VarcharType(length) if length else ct.VarcharType() + + column_type = column_type.lower().strip("()") + try: + return ct.PRIMITIVE_TYPES[column_type] + except KeyError as exc: + raise DJParseException( + f"DJ does not recognize the type `{ctx.getText()}`.", + ) from exc + + +@visit.register +def _(ctx: sbp.ComplexDataTypeContext) -> ct.ColumnType: + if ctx.ARRAY(): + return ct.ListType(visit(ctx.dataType())[0]) + if ctx.MAP(): + return ct.MapType(*visit(ctx.dataType())) + if ctx.STRUCT(): + if type_list := ctx.complexColTypeList(): + return ct.StructType(*visit(type_list)) + else: + return ct.StructType() + + +@visit.register +def _(ctx: sbp.ComplexColTypeListContext) -> List[ct.NestedField]: + return visit(ctx.complexColType()) + + +@visit.register +def _(ctx: sbp.ComplexColTypeContext) -> ct.NestedField: + name = visit(ctx.identifier()) + type = visit(ctx.dataType()) + optional = not ctx.NOT() + doc = None + if comment := ctx.commentSpec(): + doc = comment.getText() + return ct.NestedField(name, type, optional, doc) + + +@visit.register +def _(ctx: sbp.YearMonthIntervalDataTypeContext) -> ct.YearMonthIntervalType: + from_ = ctx.from_.getText().upper() + to = ctx.to.text.upper() if ctx.to else None + return ct.YearMonthIntervalType(from_=from_, to_=to) + + +@visit.register +def _(ctx: sbp.DayTimeIntervalDataTypeContext) -> ct.DayTimeIntervalType: + from_ = ctx.from_.text.upper() + to = ctx.to.text.upper() if ctx.to else None + return ct.DayTimeIntervalType(from_=from_, to_=to) + + +@visit.register +def _(ctx: sbp.ExtractContext): + return ast.Function( + ast.Name("EXTRACT"), + args=[visit(ctx.field), visit(ctx.source)], + ) diff --git a/datajunction-server/datajunction_server/sql/parsing/backends/exceptions.py b/datajunction-server/datajunction_server/sql/parsing/backends/exceptions.py new file mode 100644 index 000000000..adb7d3590 --- /dev/null +++ b/datajunction-server/datajunction_server/sql/parsing/backends/exceptions.py @@ -0,0 +1,8 @@ +""" +defines exceptions used for backend parsing +""" +from datajunction_server.errors import DJException + + +class DJParseException(DJException): + """Exception type raised upon problem creating a DJ sql ast""" diff --git a/datajunction-server/datajunction_server/sql/parsing/backends/grammar/SqlBaseLexer.g4 b/datajunction-server/datajunction_server/sql/parsing/backends/grammar/SqlBaseLexer.g4 new file mode 100644 index 000000000..9eebef406 --- /dev/null +++ b/datajunction-server/datajunction_server/sql/parsing/backends/grammar/SqlBaseLexer.g4 @@ -0,0 +1,456 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is an adaptation of Presto's presto-parser/src/main/antlr4/com/facebook/presto/sql/parser/SqlBase.g4 grammar. + */ + +lexer grammar SqlBaseLexer; + +SEMICOLON: ';'; + +LEFT_PAREN: '('; +RIGHT_PAREN: ')'; +COMMA: ','; +DOT: '.'; +LEFT_BRACKET: '['; +RIGHT_BRACKET: ']'; + +// NOTE: If you add a new token in the list below, you should update the list of keywords +// and reserved tag in `docs/sql-ref-ansi-compliance.md#sql-keywords`. + +//============================ +// Start of the keywords list +//============================ +//--SPARK-KEYWORD-LIST-START +ADD: 'ADD'; +AFTER: 'AFTER'; +ALL: 'ALL'; +ALTER: 'ALTER'; +ALWAYS: 'ALWAYS'; +ANALYZE: 'ANALYZE'; +AND: 'AND'; +ANTI: 'ANTI'; +ANY: 'ANY'; +ANY_VALUE: 'ANY_VALUE'; +ARCHIVE: 'ARCHIVE'; +ARRAY: 'ARRAY'; +AS: 'AS'; +ASC: 'ASC'; +AT: 'AT'; +AUTHORIZATION: 'AUTHORIZATION'; +BETWEEN: 'BETWEEN'; +BOTH: 'BOTH'; +BUCKET: 'BUCKET'; +BUCKETS: 'BUCKETS'; +BY: 'BY'; +CACHE: 'CACHE'; +CASCADE: 'CASCADE'; +CASE: 'CASE'; +CAST: 'CAST'; +CATALOG: 'CATALOG'; +CATALOGS: 'CATALOGS'; +CHANGE: 'CHANGE'; +CHECK: 'CHECK'; +CLEAR: 'CLEAR'; +CLUSTER: 'CLUSTER'; +CLUSTERED: 'CLUSTERED'; +CODEGEN: 'CODEGEN'; +COLLATE: 'COLLATE'; +COLLECTION: 'COLLECTION'; +COLUMN: 'COLUMN'; +COLUMNS: 'COLUMNS'; +COMMENT: 'COMMENT'; +COMMIT: 'COMMIT'; +COMPACT: 'COMPACT'; +COMPACTIONS: 'COMPACTIONS'; +COMPUTE: 'COMPUTE'; +CONCATENATE: 'CONCATENATE'; +CONSTRAINT: 'CONSTRAINT'; +COST: 'COST'; +CREATE: 'CREATE'; +CROSS: 'CROSS'; +CUBE: 'CUBE'; +CURRENT: 'CURRENT'; +CURRENT_DATE: 'CURRENT_DATE'; +CURRENT_TIME: 'CURRENT_TIME'; +CURRENT_TIMESTAMP: 'CURRENT_TIMESTAMP'; +CURRENT_USER: 'CURRENT_USER'; +DAY: 'DAY'; +DAYS: 'DAYS'; +DAYOFYEAR: 'DAYOFYEAR'; +DATA: 'DATA'; +DATABASE: 'DATABASE'; +DATABASES: 'DATABASES'; +DATEADD: 'DATEADD'; +DATEDIFF: 'DATEDIFF'; +DBPROPERTIES: 'DBPROPERTIES'; +DEFAULT: 'DEFAULT'; +DEFINED: 'DEFINED'; +DELETE: 'DELETE'; +DELIMITED: 'DELIMITED'; +DESC: 'DESC'; +DESCRIBE: 'DESCRIBE'; +DFS: 'DFS'; +DIRECTORIES: 'DIRECTORIES'; +DIRECTORY: 'DIRECTORY'; +DISTINCT: 'DISTINCT'; +DISTRIBUTE: 'DISTRIBUTE'; +DIV: 'DIV'; +DROP: 'DROP'; +ELSE: 'ELSE'; +END: 'END'; +ESCAPE: 'ESCAPE'; +ESCAPED: 'ESCAPED'; +EXCEPT: 'EXCEPT'; +EXCHANGE: 'EXCHANGE'; +EXCLUDE: 'EXCLUDE'; +EXISTS: 'EXISTS'; +EXPLAIN: 'EXPLAIN'; +EXPORT: 'EXPORT'; +EXTENDED: 'EXTENDED'; +EXTERNAL: 'EXTERNAL'; +EXTRACT: 'EXTRACT'; +FALSE: 'FALSE'; +FETCH: 'FETCH'; +FIELDS: 'FIELDS'; +FILTER: 'FILTER'; +FILEFORMAT: 'FILEFORMAT'; +FIRST: 'FIRST'; +FOLLOWING: 'FOLLOWING'; +FOR: 'FOR'; +FOREIGN: 'FOREIGN'; +FORMAT: 'FORMAT'; +FORMATTED: 'FORMATTED'; +FROM: 'FROM'; +FULL: 'FULL'; +FUNCTION: 'FUNCTION'; +FUNCTIONS: 'FUNCTIONS'; +GENERATED: 'GENERATED'; +GLOBAL: 'GLOBAL'; +GRANT: 'GRANT'; +GROUP: 'GROUP'; +GROUPING: 'GROUPING'; +HAVING: 'HAVING'; +HOUR: 'HOUR'; +HOURS: 'HOURS'; +IF: 'IF'; +IGNORE: 'IGNORE'; +IMPORT: 'IMPORT'; +IN: 'IN'; +INCLUDE: 'INCLUDE'; +INDEX: 'INDEX'; +INDEXES: 'INDEXES'; +INNER: 'INNER'; +INPATH: 'INPATH'; +INPUTFORMAT: 'INPUTFORMAT'; +INSERT: 'INSERT'; +INTERSECT: 'INTERSECT'; +INTERVAL: 'INTERVAL'; +INTO: 'INTO'; +IS: 'IS'; +ITEMS: 'ITEMS'; +JOIN: 'JOIN'; +KEYS: 'KEYS'; +LAST: 'LAST'; +LATERAL: 'LATERAL'; +LAZY: 'LAZY'; +LEADING: 'LEADING'; +LEFT: 'LEFT'; +LIKE: 'LIKE'; +ILIKE: 'ILIKE'; +LIMIT: 'LIMIT'; +LINES: 'LINES'; +LIST: 'LIST'; +LOAD: 'LOAD'; +LOCAL: 'LOCAL'; +LOCATION: 'LOCATION'; +LOCK: 'LOCK'; +LOCKS: 'LOCKS'; +LOGICAL: 'LOGICAL'; +MACRO: 'MACRO'; +MAP: 'MAP'; +MATCHED: 'MATCHED'; +MERGE: 'MERGE'; +MICROSECOND: 'MICROSECOND'; +MICROSECONDS: 'MICROSECONDS'; +MILLISECOND: 'MILLISECOND'; +MILLISECONDS: 'MILLISECONDS'; +MINUTE: 'MINUTE'; +MINUTES: 'MINUTES'; +MONTH: 'MONTH'; +MONTHS: 'MONTHS'; +MSCK: 'MSCK'; +NAMESPACE: 'NAMESPACE'; +NAMESPACES: 'NAMESPACES'; +NANOSECOND: 'NANOSECOND'; +NANOSECONDS: 'NANOSECONDS'; +NATURAL: 'NATURAL'; +NO: 'NO'; +NOT: 'NOT' | '!'; +NULL: 'NULL'; +NULLS: 'NULLS'; +OF: 'OF'; +OFFSET: 'OFFSET'; +ON: 'ON'; +ONLY: 'ONLY'; +OPTION: 'OPTION'; +OPTIONS: 'OPTIONS'; +OR: 'OR'; +ORDER: 'ORDER'; +OUT: 'OUT'; +OUTER: 'OUTER'; +OUTPUTFORMAT: 'OUTPUTFORMAT'; +OVER: 'OVER'; +OVERLAPS: 'OVERLAPS'; +OVERLAY: 'OVERLAY'; +OVERWRITE: 'OVERWRITE'; +PARTITION: 'PARTITION'; +PARTITIONED: 'PARTITIONED'; +PARTITIONS: 'PARTITIONS'; +PERCENTILE_CONT: 'PERCENTILE_CONT'; +PERCENTILE_DISC: 'PERCENTILE_DISC'; +PERCENTLIT: 'PERCENT'; +PIVOT: 'PIVOT'; +PLACING: 'PLACING'; +POSITION: 'POSITION'; +PRECEDING: 'PRECEDING'; +PRIMARY: 'PRIMARY'; +PRINCIPALS: 'PRINCIPALS'; +PROPERTIES: 'PROPERTIES'; +PURGE: 'PURGE'; +QUARTER: 'QUARTER'; +QUERY: 'QUERY'; +RANGE: 'RANGE'; +RECORDREADER: 'RECORDREADER'; +RECORDWRITER: 'RECORDWRITER'; +RECOVER: 'RECOVER'; +REDUCE: 'REDUCE'; +REFERENCES: 'REFERENCES'; +REFRESH: 'REFRESH'; +RENAME: 'RENAME'; +REPAIR: 'REPAIR'; +REPEATABLE: 'REPEATABLE'; +REPLACE: 'REPLACE'; +RESET: 'RESET'; +RESPECT: 'RESPECT'; +RESTRICT: 'RESTRICT'; +REVOKE: 'REVOKE'; +RIGHT: 'RIGHT'; +RLIKE: 'RLIKE' | 'REGEXP'; +ROLE: 'ROLE'; +ROLES: 'ROLES'; +ROLLBACK: 'ROLLBACK'; +ROLLUP: 'ROLLUP'; +ROW: 'ROW'; +ROWS: 'ROWS'; +SECOND: 'SECOND'; +SECONDS: 'SECONDS'; +SCHEMA: 'SCHEMA'; +SCHEMAS: 'SCHEMAS'; +SELECT: 'SELECT'; +SEMI: 'SEMI'; +SEPARATED: 'SEPARATED'; +SERDE: 'SERDE'; +SERDEPROPERTIES: 'SERDEPROPERTIES'; +SESSION_USER: 'SESSION_USER'; +SET: 'SET'; +SETMINUS: 'MINUS'; +SETS: 'SETS'; +SHOW: 'SHOW'; +SKEWED: 'SKEWED'; +SOME: 'SOME'; +SORT: 'SORT'; +SORTED: 'SORTED'; +SOURCE: 'SOURCE'; +START: 'START'; +STATISTICS: 'STATISTICS'; +STORED: 'STORED'; +STRATIFY: 'STRATIFY'; +STRUCT: 'STRUCT'; +SUBSTR: 'SUBSTR'; +SUBSTRING: 'SUBSTRING'; +SYNC: 'SYNC'; +SYSTEM_TIME: 'SYSTEM_TIME'; +SYSTEM_VERSION: 'SYSTEM_VERSION'; +TABLE: 'TABLE'; +TABLES: 'TABLES'; +TABLESAMPLE: 'TABLESAMPLE'; +TARGET: 'TARGET'; +TBLPROPERTIES: 'TBLPROPERTIES'; +TEMPORARY: 'TEMPORARY' | 'TEMP'; +TERMINATED: 'TERMINATED'; +THEN: 'THEN'; +TIME: 'TIME'; +TIMESTAMP: 'TIMESTAMP'; +TIMESTAMPADD: 'TIMESTAMPADD'; +TIMESTAMPDIFF: 'TIMESTAMPDIFF'; +TO: 'TO'; +TOUCH: 'TOUCH'; +TRAILING: 'TRAILING'; +TRANSACTION: 'TRANSACTION'; +TRANSACTIONS: 'TRANSACTIONS'; +TRANSFORM: 'TRANSFORM'; +TRIM: 'TRIM'; +TRUE: 'TRUE'; +TRUNCATE: 'TRUNCATE'; +TRY_CAST: 'TRY_CAST'; +TYPE: 'TYPE'; +UNARCHIVE: 'UNARCHIVE'; +UNBOUNDED: 'UNBOUNDED'; +UNCACHE: 'UNCACHE'; +UNION: 'UNION'; +UNIQUE: 'UNIQUE'; +UNKNOWN: 'UNKNOWN'; +UNLOCK: 'UNLOCK'; +UNPIVOT: 'UNPIVOT'; +UNSET: 'UNSET'; +UPDATE: 'UPDATE'; +USE: 'USE'; +USER: 'USER'; +USING: 'USING'; +VALUES: 'VALUES'; +VERSION: 'VERSION'; +VIEW: 'VIEW'; +VIEWS: 'VIEWS'; +WEEK: 'WEEK'; +WEEKS: 'WEEKS'; +WHEN: 'WHEN'; +WHERE: 'WHERE'; +WINDOW: 'WINDOW'; +WITH: 'WITH'; +WITHIN: 'WITHIN'; +YEAR: 'YEAR'; +YEARS: 'YEARS'; +ZONE: 'ZONE'; +//--SPARK-KEYWORD-LIST-END +//============================ +// End of the keywords list +//============================ + +EQ : '=' | '=='; +NSEQ: '<=>'; +NEQ : '<>'; +NEQJ: '!='; +LT : '<'; +LTE : '<=' | '!>'; +GT : '>'; +GTE : '>=' | '!<'; + +PLUS: '+'; +MINUS: '-'; +ASTERISK: '*'; +SLASH: '/'; +PERCENT: '%'; +TILDE: '~'; +AMPERSAND: '&'; +PIPE: '|'; +CONCAT_PIPE: '||'; +HAT: '^'; +COLON: ':'; +ARROW: '->'; +HENT_START: '/*+'; +HENT_END: '*/'; + +STRING + : '\'' ( ~('\''|'\\') | ('\\' .) )* '\'' + | 'R\'' (~'\'')* '\'' + | 'R"'(~'"')* '"' + ; + +DOUBLEQUOTED_STRING + :'"' ( ~('"'|'\\') | ('\\' .) )* '"' + ; + +BIGINT_LITERAL + : DIGIT+ 'L' + ; + +SMALLINT_LITERAL + : DIGIT+ 'S' + ; + +TINYINT_LITERAL + : DIGIT+ 'Y' + ; + +INTEGER_VALUE + : DIGIT+ + ; + +EXPONENT_VALUE + : DIGIT+ EXPONENT + | DECIMAL_DIGITS EXPONENT + ; + +DECIMAL_VALUE + : DECIMAL_DIGITS + ; + +FLOAT_LITERAL + : DIGIT+ EXPONENT? 'F' + | DECIMAL_DIGITS EXPONENT? 'F' + ; + +DOUBLE_LITERAL + : DIGIT+ EXPONENT? 'D' + | DECIMAL_DIGITS EXPONENT? 'D' + ; + +BIGDECIMAL_LITERAL + : DIGIT+ EXPONENT? 'BD' + | DECIMAL_DIGITS EXPONENT? 'BD' + ; + +IDENTIFIER + : (LETTER | DIGIT | '_')+ + ; + +BACKQUOTED_IDENTIFIER + : '`' ( ~'`' | '``' )* '`' + ; + +fragment DECIMAL_DIGITS + : DIGIT+ '.' DIGIT* + | '.' DIGIT+ + ; + +fragment EXPONENT + : 'E' [+-]? DIGIT+ + ; + +fragment DIGIT + : [0-9] + ; + +fragment LETTER + : [A-Z] + ; + +SIMPLE_COMMENT + : '--' ('\\\n' | ~[\r\n])* '\r'? '\n'? -> channel(HIDDEN) + ; + +BRACKETED_COMMENT + : '/*' ( BRACKETED_COMMENT | . )*? ('*/' | {markUnclosedComment();} EOF) -> channel(HIDDEN) + ; + +WS + : [ \r\n\t]+ -> channel(HIDDEN) + ; + +// Catch-all for anything we can't recognize. +// We use this to be able to ignore and recover all the text +// when splitting statements with DelimiterLexer +UNRECOGNIZED + : . + ; diff --git a/datajunction-server/datajunction_server/sql/parsing/backends/grammar/SqlBaseParser.g4 b/datajunction-server/datajunction_server/sql/parsing/backends/grammar/SqlBaseParser.g4 new file mode 100644 index 000000000..38fa9b889 --- /dev/null +++ b/datajunction-server/datajunction_server/sql/parsing/backends/grammar/SqlBaseParser.g4 @@ -0,0 +1,1714 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is an adaptation of Presto's presto-parser/src/main/antlr4/com/facebook/presto/sql/parser/SqlBase.g4 grammar. + */ + +parser grammar SqlBaseParser; + +options { tokenVocab = SqlBaseLexer; } + +singleStatement + : statement SEMICOLON* EOF + ; + +singleExpression + : namedExpression EOF + ; + +singleTableIdentifier + : tableIdentifier EOF + ; + +singleMultipartIdentifier + : multipartIdentifier EOF + ; + +singleFunctionIdentifier + : functionIdentifier EOF + ; + +singleDataType + : dataType EOF + ; + +singleTableSchema + : colTypeList EOF + ; + +statement + : query #statementDefault + | ctes? dmlStatementNoWith #dmlStatement + | USE multipartIdentifier #use + | USE namespace multipartIdentifier #useNamespace + | SET CATALOG (identifier | stringLit) #setCatalog + | CREATE namespace (IF NOT EXISTS)? multipartIdentifier + (commentSpec | + locationSpec | + (WITH (DBPROPERTIES | PROPERTIES) propertyList))* #createNamespace + | ALTER namespace multipartIdentifier + SET (DBPROPERTIES | PROPERTIES) propertyList #setNamespaceProperties + | ALTER namespace multipartIdentifier + SET locationSpec #setNamespaceLocation + | DROP namespace (IF EXISTS)? multipartIdentifier + (RESTRICT | CASCADE)? #dropNamespace + | SHOW namespaces ((FROM | IN) multipartIdentifier)? + (LIKE? pattern=stringLit)? #showNamespaces + | createTableHeader (LEFT_PAREN createOrReplaceTableColTypeList RIGHT_PAREN)? tableProvider? + createTableClauses + (AS? query)? #createTable + | CREATE TABLE (IF NOT EXISTS)? target=tableIdentifier + LIKE source=tableIdentifier + (tableProvider | + rowFormat | + createFileFormat | + locationSpec | + (TBLPROPERTIES tableProps=propertyList))* #createTableLike + | replaceTableHeader (LEFT_PAREN createOrReplaceTableColTypeList RIGHT_PAREN)? tableProvider? + createTableClauses + (AS? query)? #replaceTable + | ANALYZE TABLE multipartIdentifier partitionSpec? COMPUTE STATISTICS + (identifier | FOR COLUMNS identifierSeq | FOR ALL COLUMNS)? #analyze + | ANALYZE TABLES ((FROM | IN) multipartIdentifier)? COMPUTE STATISTICS + (identifier)? #analyzeTables + | ALTER TABLE multipartIdentifier + ADD (COLUMN | COLUMNS) + columns=qualifiedColTypeWithPositionList #addTableColumns + | ALTER TABLE multipartIdentifier + ADD (COLUMN | COLUMNS) + LEFT_PAREN columns=qualifiedColTypeWithPositionList RIGHT_PAREN #addTableColumns + | ALTER TABLE table=multipartIdentifier + RENAME COLUMN + from=multipartIdentifier TO to=errorCapturingIdentifier #renameTableColumn + | ALTER TABLE multipartIdentifier + DROP (COLUMN | COLUMNS) (IF EXISTS)? + LEFT_PAREN columns=multipartIdentifierList RIGHT_PAREN #dropTableColumns + | ALTER TABLE multipartIdentifier + DROP (COLUMN | COLUMNS) (IF EXISTS)? + columns=multipartIdentifierList #dropTableColumns + | ALTER (TABLE | VIEW) from=multipartIdentifier + RENAME TO to=multipartIdentifier #renameTable + | ALTER (TABLE | VIEW) multipartIdentifier + SET TBLPROPERTIES propertyList #setTableProperties + | ALTER (TABLE | VIEW) multipartIdentifier + UNSET TBLPROPERTIES (IF EXISTS)? propertyList #unsetTableProperties + | ALTER TABLE table=multipartIdentifier + (ALTER | CHANGE) COLUMN? column=multipartIdentifier + alterColumnAction? #alterTableAlterColumn + | ALTER TABLE table=multipartIdentifier partitionSpec? + CHANGE COLUMN? + colName=multipartIdentifier colType colPosition? #hiveChangeColumn + | ALTER TABLE table=multipartIdentifier partitionSpec? + REPLACE COLUMNS + LEFT_PAREN columns=qualifiedColTypeWithPositionList + RIGHT_PAREN #hiveReplaceColumns + | ALTER TABLE multipartIdentifier (partitionSpec)? + SET SERDE stringLit (WITH SERDEPROPERTIES propertyList)? #setTableSerDe + | ALTER TABLE multipartIdentifier (partitionSpec)? + SET SERDEPROPERTIES propertyList #setTableSerDe + | ALTER (TABLE | VIEW) multipartIdentifier ADD (IF NOT EXISTS)? + partitionSpecLocation+ #addTablePartition + | ALTER TABLE multipartIdentifier + from=partitionSpec RENAME TO to=partitionSpec #renameTablePartition + | ALTER (TABLE | VIEW) multipartIdentifier + DROP (IF EXISTS)? partitionSpec (COMMA partitionSpec)* PURGE? #dropTablePartitions + | ALTER TABLE multipartIdentifier + (partitionSpec)? SET locationSpec #setTableLocation + | ALTER TABLE multipartIdentifier RECOVER PARTITIONS #recoverPartitions + | DROP TABLE (IF EXISTS)? multipartIdentifier PURGE? #dropTable + | DROP VIEW (IF EXISTS)? multipartIdentifier #dropView + | CREATE (OR REPLACE)? (GLOBAL? TEMPORARY)? + VIEW (IF NOT EXISTS)? multipartIdentifier + identifierCommentList? + (commentSpec | + (PARTITIONED ON identifierList) | + (TBLPROPERTIES propertyList))* + AS query #createView + | CREATE (OR REPLACE)? GLOBAL? TEMPORARY VIEW + tableIdentifier (LEFT_PAREN colTypeList RIGHT_PAREN)? tableProvider + (OPTIONS propertyList)? #createTempViewUsing + | ALTER VIEW multipartIdentifier AS? query #alterViewQuery + | CREATE (OR REPLACE)? TEMPORARY? FUNCTION (IF NOT EXISTS)? + multipartIdentifier AS className=stringLit + (USING resource (COMMA resource)*)? #createFunction + | DROP TEMPORARY? FUNCTION (IF EXISTS)? multipartIdentifier #dropFunction + | EXPLAIN (LOGICAL | FORMATTED | EXTENDED | CODEGEN | COST)? + statement #explain + | SHOW TABLES ((FROM | IN) multipartIdentifier)? + (LIKE? pattern=stringLit)? #showTables + | SHOW TABLE EXTENDED ((FROM | IN) ns=multipartIdentifier)? + LIKE pattern=stringLit partitionSpec? #showTableExtended + | SHOW TBLPROPERTIES table=multipartIdentifier + (LEFT_PAREN key=propertyKey RIGHT_PAREN)? #showTblProperties + | SHOW COLUMNS (FROM | IN) table=multipartIdentifier + ((FROM | IN) ns=multipartIdentifier)? #showColumns + | SHOW VIEWS ((FROM | IN) multipartIdentifier)? + (LIKE? pattern=stringLit)? #showViews + | SHOW PARTITIONS multipartIdentifier partitionSpec? #showPartitions + | SHOW identifier? FUNCTIONS ((FROM | IN) ns=multipartIdentifier)? + (LIKE? (legacy=multipartIdentifier | pattern=stringLit))? #showFunctions + | SHOW CREATE TABLE multipartIdentifier (AS SERDE)? #showCreateTable + | SHOW CURRENT namespace #showCurrentNamespace + | SHOW CATALOGS (LIKE? pattern=stringLit)? #showCatalogs + | (DESC | DESCRIBE) FUNCTION EXTENDED? describeFuncName #describeFunction + | (DESC | DESCRIBE) namespace EXTENDED? + multipartIdentifier #describeNamespace + | (DESC | DESCRIBE) TABLE? option=(EXTENDED | FORMATTED)? + multipartIdentifier partitionSpec? describeColName? #describeRelation + | (DESC | DESCRIBE) QUERY? query #describeQuery + | COMMENT ON namespace multipartIdentifier IS + comment #commentNamespace + | COMMENT ON TABLE multipartIdentifier IS comment #commentTable + | REFRESH TABLE multipartIdentifier #refreshTable + | REFRESH FUNCTION multipartIdentifier #refreshFunction + | REFRESH (stringLit | .*?) #refreshResource + | CACHE LAZY? TABLE multipartIdentifier + (OPTIONS options=propertyList)? (AS? query)? #cacheTable + | UNCACHE TABLE (IF EXISTS)? multipartIdentifier #uncacheTable + | CLEAR CACHE #clearCache + | LOAD DATA LOCAL? INPATH path=stringLit OVERWRITE? INTO TABLE + multipartIdentifier partitionSpec? #loadData + | TRUNCATE TABLE multipartIdentifier partitionSpec? #truncateTable + | (MSCK)? REPAIR TABLE multipartIdentifier + (option=(ADD|DROP|SYNC) PARTITIONS)? #repairTable + | op=(ADD | LIST) identifier .*? #manageResource + | SET ROLE .*? #failNativeCommand + | SET TIME ZONE interval #setTimeZone + | SET TIME ZONE timezone #setTimeZone + | SET TIME ZONE .*? #setTimeZone + | SET configKey EQ configValue #setQuotedConfiguration + | SET configKey (EQ .*?)? #setConfiguration + | SET .*? EQ configValue #setQuotedConfiguration + | SET .*? #setConfiguration + | RESET configKey #resetQuotedConfiguration + | RESET .*? #resetConfiguration + | CREATE INDEX (IF NOT EXISTS)? identifier ON TABLE? + multipartIdentifier (USING indexType=identifier)? + LEFT_PAREN columns=multipartIdentifierPropertyList RIGHT_PAREN + (OPTIONS options=propertyList)? #createIndex + | DROP INDEX (IF EXISTS)? identifier ON TABLE? multipartIdentifier #dropIndex + | unsupportedHiveNativeCommands .*? #failNativeCommand + ; + +timezone + : stringLit + | LOCAL + ; + +configKey + : quotedIdentifier + ; + +configValue + : backQuotedIdentifier + ; + +unsupportedHiveNativeCommands + : kw1=CREATE kw2=ROLE + | kw1=DROP kw2=ROLE + | kw1=GRANT kw2=ROLE? + | kw1=REVOKE kw2=ROLE? + | kw1=SHOW kw2=GRANT + | kw1=SHOW kw2=ROLE kw3=GRANT? + | kw1=SHOW kw2=PRINCIPALS + | kw1=SHOW kw2=ROLES + | kw1=SHOW kw2=CURRENT kw3=ROLES + | kw1=EXPORT kw2=TABLE + | kw1=IMPORT kw2=TABLE + | kw1=SHOW kw2=COMPACTIONS + | kw1=SHOW kw2=CREATE kw3=TABLE + | kw1=SHOW kw2=TRANSACTIONS + | kw1=SHOW kw2=INDEXES + | kw1=SHOW kw2=LOCKS + | kw1=CREATE kw2=INDEX + | kw1=DROP kw2=INDEX + | kw1=ALTER kw2=INDEX + | kw1=LOCK kw2=TABLE + | kw1=LOCK kw2=DATABASE + | kw1=UNLOCK kw2=TABLE + | kw1=UNLOCK kw2=DATABASE + | kw1=CREATE kw2=TEMPORARY kw3=MACRO + | kw1=DROP kw2=TEMPORARY kw3=MACRO + | kw1=ALTER kw2=TABLE tableIdentifier kw3=NOT kw4=CLUSTERED + | kw1=ALTER kw2=TABLE tableIdentifier kw3=CLUSTERED kw4=BY + | kw1=ALTER kw2=TABLE tableIdentifier kw3=NOT kw4=SORTED + | kw1=ALTER kw2=TABLE tableIdentifier kw3=SKEWED kw4=BY + | kw1=ALTER kw2=TABLE tableIdentifier kw3=NOT kw4=SKEWED + | kw1=ALTER kw2=TABLE tableIdentifier kw3=NOT kw4=STORED kw5=AS kw6=DIRECTORIES + | kw1=ALTER kw2=TABLE tableIdentifier kw3=SET kw4=SKEWED kw5=LOCATION + | kw1=ALTER kw2=TABLE tableIdentifier kw3=EXCHANGE kw4=PARTITION + | kw1=ALTER kw2=TABLE tableIdentifier kw3=ARCHIVE kw4=PARTITION + | kw1=ALTER kw2=TABLE tableIdentifier kw3=UNARCHIVE kw4=PARTITION + | kw1=ALTER kw2=TABLE tableIdentifier kw3=TOUCH + | kw1=ALTER kw2=TABLE tableIdentifier partitionSpec? kw3=COMPACT + | kw1=ALTER kw2=TABLE tableIdentifier partitionSpec? kw3=CONCATENATE + | kw1=ALTER kw2=TABLE tableIdentifier partitionSpec? kw3=SET kw4=FILEFORMAT + | kw1=ALTER kw2=TABLE tableIdentifier partitionSpec? kw3=REPLACE kw4=COLUMNS + | kw1=START kw2=TRANSACTION + | kw1=COMMIT + | kw1=ROLLBACK + | kw1=DFS + ; + +createTableHeader + : CREATE TEMPORARY? EXTERNAL? TABLE (IF NOT EXISTS)? multipartIdentifier + ; + +replaceTableHeader + : (CREATE OR)? REPLACE TABLE multipartIdentifier + ; + +bucketSpec + : CLUSTERED BY identifierList + (SORTED BY orderedIdentifierList)? + INTO INTEGER_VALUE BUCKETS + ; + +skewSpec + : SKEWED BY identifierList + ON (constantList | nestedConstantList) + (STORED AS DIRECTORIES)? + ; + +locationSpec + : LOCATION stringLit + ; + +commentSpec + : COMMENT stringLit + ; + + +insertInto + : INSERT OVERWRITE TABLE? multipartIdentifier (partitionSpec (IF NOT EXISTS)?)? identifierList? #insertOverwriteTable + | INSERT INTO TABLE? multipartIdentifier partitionSpec? (IF NOT EXISTS)? identifierList? #insertIntoTable + | INSERT INTO TABLE? multipartIdentifier REPLACE whereClause #insertIntoReplaceWhere + | INSERT OVERWRITE LOCAL? DIRECTORY path=stringLit rowFormat? createFileFormat? #insertOverwriteHiveDir + | INSERT OVERWRITE LOCAL? DIRECTORY (path=stringLit)? tableProvider (OPTIONS options=propertyList)? #insertOverwriteDir + ; + +partitionSpecLocation + : partitionSpec locationSpec? + ; + +partitionSpec + : PARTITION LEFT_PAREN partitionVal (COMMA partitionVal)* RIGHT_PAREN + ; + +partitionVal + : identifier (EQ constant)? + | identifier EQ DEFAULT + ; + +namespace + : NAMESPACE + | DATABASE + | SCHEMA + ; + +namespaces + : NAMESPACES + | DATABASES + | SCHEMAS + ; + +describeFuncName + : qualifiedName + | stringLit + | comparisonOperator + | arithmeticOperator + | predicateOperator + ; + +describeColName + : nameParts+=identifier (DOT nameParts+=identifier)* + ; + +ctes + : WITH namedQuery (COMMA namedQuery)* + ; + +query + : ctes? queryTerm queryOrganization + ; + +namedQuery + : name=errorCapturingIdentifier (columnAliases=identifierList)? AS? LEFT_PAREN query RIGHT_PAREN + ; + +queryTerm + : queryPrimary #queryTermDefault + | left=queryTerm + operator=(INTERSECT | UNION | EXCEPT | SETMINUS) setQuantifier? right=queryTerm #setOperation + ; + +querySpecification + : transformClause + fromClause? + lateralView* + whereClause? + aggregationClause? + havingClause? + windowClause? #transformQuerySpecification + | selectClause + fromClause? + lateralView* + whereClause? + aggregationClause? + havingClause? + windowClause? #regularQuerySpecification + ; + +queryPrimary + : querySpecification #queryPrimaryDefault + | fromStatement #fromStmt + | TABLE multipartIdentifier #table + | inlineTable #inlineTableDefault1 + | LEFT_PAREN query RIGHT_PAREN #subquery + ; + +tableProvider + : USING multipartIdentifier + ; + +createTableClauses + :((OPTIONS options=propertyList) | + (PARTITIONED BY partitioning=partitionFieldList) | + skewSpec | + bucketSpec | + rowFormat | + createFileFormat | + locationSpec | + commentSpec | + (TBLPROPERTIES tableProps=propertyList))* + ; + +propertyList + : LEFT_PAREN property (COMMA property)* RIGHT_PAREN + ; + +property + : key=propertyKey (EQ? value=propertyValue)? + ; + +propertyKey + : identifier (DOT identifier)* + | stringLit + ; + +propertyValue + : INTEGER_VALUE + | DECIMAL_VALUE + | booleanValue + | stringLit + ; + +constantList + : LEFT_PAREN constant (COMMA constant)* RIGHT_PAREN + ; + +nestedConstantList + : LEFT_PAREN constantList (COMMA constantList)* RIGHT_PAREN + ; + +createFileFormat + : STORED AS fileFormat + | STORED BY storageHandler + ; + +fileFormat + : INPUTFORMAT inFmt=stringLit OUTPUTFORMAT outFmt=stringLit #tableFileFormat + | identifier #genericFileFormat + ; + +storageHandler + : stringLit (WITH SERDEPROPERTIES propertyList)? + ; + +resource + : identifier stringLit + ; + +dmlStatementNoWith + : insertInto query #singleInsertQuery + | fromClause multiInsertQueryBody+ #multiInsertQuery + | DELETE FROM multipartIdentifier tableAlias whereClause? #deleteFromTable + | UPDATE multipartIdentifier tableAlias setClause whereClause? #updateTable + | MERGE INTO target=multipartIdentifier targetAlias=tableAlias + USING (source=multipartIdentifier | + LEFT_PAREN sourceQuery=query RIGHT_PAREN) sourceAlias=tableAlias + ON mergeCondition=booleanExpression + matchedClause* + notMatchedClause* + notMatchedBySourceClause* #mergeIntoTable + ; + +queryOrganization + : (ORDER BY order+=sortItem (COMMA order+=sortItem)*)? + (CLUSTER BY clusterBy+=expression (COMMA clusterBy+=expression)*)? + (DISTRIBUTE BY distributeBy+=expression (COMMA distributeBy+=expression)*)? + (SORT BY sort+=sortItem (COMMA sort+=sortItem)*)? + windowClause? + (LIMIT (ALL | limit=expression))? + (OFFSET offset=expression)? + ; + +multiInsertQueryBody + : insertInto fromStatementBody + ; + +sortItem + : expression ordering=(ASC | DESC)? (NULLS nullOrder=(LAST | FIRST))? + ; + +fromStatement + : fromClause fromStatementBody+ + ; + +fromStatementBody + : transformClause + whereClause? + queryOrganization + | selectClause + lateralView* + whereClause? + aggregationClause? + havingClause? + windowClause? + queryOrganization + ; + + + +transformClause + : (SELECT kind=TRANSFORM LEFT_PAREN setQuantifier? expressionSeq RIGHT_PAREN + | kind=MAP setQuantifier? expressionSeq + | kind=REDUCE setQuantifier? expressionSeq) + inRowFormat=rowFormat? + (RECORDWRITER recordWriter=stringLit)? + USING script=stringLit + (AS (identifierSeq | colTypeList | (LEFT_PAREN (identifierSeq | colTypeList) RIGHT_PAREN)))? + outRowFormat=rowFormat? + (RECORDREADER recordReader=stringLit)? + ; + +selectClause + : SELECT (hints+=hint)* setQuantifier? namedExpressionSeq + ; + +setClause + : SET assignmentList + ; + +matchedClause + : WHEN MATCHED (AND matchedCond=booleanExpression)? THEN matchedAction + ; +notMatchedClause + : WHEN NOT MATCHED (BY TARGET)? (AND notMatchedCond=booleanExpression)? THEN notMatchedAction + ; + +notMatchedBySourceClause + : WHEN NOT MATCHED BY SOURCE (AND notMatchedBySourceCond=booleanExpression)? THEN notMatchedBySourceAction + ; + +matchedAction + : DELETE + | UPDATE SET ASTERISK + | UPDATE SET assignmentList + ; + +notMatchedAction + : INSERT ASTERISK + | INSERT LEFT_PAREN columns=multipartIdentifierList RIGHT_PAREN + VALUES LEFT_PAREN expression (COMMA expression)* RIGHT_PAREN + ; + +notMatchedBySourceAction + : DELETE + | UPDATE SET assignmentList + ; + +assignmentList + : assignment (COMMA assignment)* + ; + +assignment + : key=multipartIdentifier EQ value=expression + ; + +whereClause + : WHERE booleanExpression + ; + +havingClause + : HAVING booleanExpression + ; + +hint + : HENT_START hintStatements+=hintStatement (COMMA? hintStatements+=hintStatement)* HENT_END + ; + +hintStatement + : hintName=identifier + | hintName=identifier LEFT_PAREN parameters+=primaryExpression (COMMA parameters+=primaryExpression)* RIGHT_PAREN + ; + +fromClause + : FROM relation (COMMA relation)* lateralView* pivotClause? unpivotClause? + ; + +temporalClause + : FOR? (SYSTEM_VERSION | VERSION) AS OF version + | FOR? (SYSTEM_TIME | TIMESTAMP) AS OF timestamp=valueExpression + ; + +aggregationClause + : GROUP BY groupingExpressionsWithGroupingAnalytics+=groupByClause + (COMMA groupingExpressionsWithGroupingAnalytics+=groupByClause)* + | GROUP BY groupingExpressions+=expression (COMMA groupingExpressions+=expression)* ( + WITH kind=ROLLUP + | WITH kind=CUBE + | kind=GROUPING SETS LEFT_PAREN groupingSet (COMMA groupingSet)* RIGHT_PAREN)? + ; + +groupByClause + : groupingAnalytics + | expression + ; + +groupingAnalytics + : (ROLLUP | CUBE) LEFT_PAREN groupingSet (COMMA groupingSet)* RIGHT_PAREN + | GROUPING SETS LEFT_PAREN groupingElement (COMMA groupingElement)* RIGHT_PAREN + ; + +groupingElement + : groupingAnalytics + | groupingSet + ; + +groupingSet + : LEFT_PAREN (expression (COMMA expression)*)? RIGHT_PAREN + | expression + ; + +pivotClause + : PIVOT LEFT_PAREN aggregates=namedExpressionSeq FOR pivotColumn IN LEFT_PAREN pivotValues+=pivotValue (COMMA pivotValues+=pivotValue)* RIGHT_PAREN RIGHT_PAREN + ; + +pivotColumn + : identifiers+=identifier + | LEFT_PAREN identifiers+=identifier (COMMA identifiers+=identifier)* RIGHT_PAREN + ; + +pivotValue + : expression (AS? identifier)? + ; + +unpivotClause + : UNPIVOT nullOperator=unpivotNullClause? LEFT_PAREN + operator=unpivotOperator + RIGHT_PAREN (AS? identifier)? + ; + +unpivotNullClause + : (INCLUDE | EXCLUDE) NULLS + ; + +unpivotOperator + : (unpivotSingleValueColumnClause | unpivotMultiValueColumnClause) + ; + +unpivotSingleValueColumnClause + : unpivotValueColumn FOR unpivotNameColumn IN LEFT_PAREN unpivotColumns+=unpivotColumnAndAlias (COMMA unpivotColumns+=unpivotColumnAndAlias)* RIGHT_PAREN + ; + +unpivotMultiValueColumnClause + : LEFT_PAREN unpivotValueColumns+=unpivotValueColumn (COMMA unpivotValueColumns+=unpivotValueColumn)* RIGHT_PAREN + FOR unpivotNameColumn + IN LEFT_PAREN unpivotColumnSets+=unpivotColumnSet (COMMA unpivotColumnSets+=unpivotColumnSet)* RIGHT_PAREN + ; + +unpivotColumnSet + : LEFT_PAREN unpivotColumns+=unpivotColumn (COMMA unpivotColumns+=unpivotColumn)* RIGHT_PAREN unpivotAlias? + ; + +unpivotValueColumn + : identifier + ; + +unpivotNameColumn + : identifier + ; + +unpivotColumnAndAlias + : unpivotColumn unpivotAlias? + ; + +unpivotColumn + : multipartIdentifier + ; + +unpivotAlias + : AS? identifier + ; + +lateralView + : LATERAL VIEW (OUTER)? qualifiedName LEFT_PAREN (expression (COMMA expression)*)? RIGHT_PAREN tblName=identifier (AS? colName+=identifier (COMMA colName+=identifier)*)? + ; + +setQuantifier + : DISTINCT + | ALL + ; + +relation + : LATERAL? relationPrimary relationExtension* + ; + +relationExtension + : joinRelation + | pivotClause + | unpivotClause + ; + +joinRelation + : (joinType) JOIN LATERAL? right=relationPrimary joinCriteria? + | NATURAL joinType JOIN LATERAL? right=relationPrimary + ; + +joinType + : INNER? + | CROSS + | LEFT OUTER? + | LEFT? SEMI + | RIGHT OUTER? + | FULL OUTER? + | LEFT? ANTI + ; + +joinCriteria + : ON booleanExpression + | USING identifierList + ; + +sample + : TABLESAMPLE LEFT_PAREN sampleMethod? RIGHT_PAREN (REPEATABLE LEFT_PAREN seed=INTEGER_VALUE RIGHT_PAREN)? + ; + +sampleMethod + : negativeSign=MINUS? percentage=(INTEGER_VALUE | DECIMAL_VALUE) PERCENTLIT #sampleByPercentile + | expression ROWS #sampleByRows + | sampleType=BUCKET numerator=INTEGER_VALUE OUT OF denominator=INTEGER_VALUE + (ON (identifier | qualifiedName LEFT_PAREN RIGHT_PAREN))? #sampleByBucket + | bytes=expression #sampleByBytes + ; + +identifierList + : LEFT_PAREN identifierSeq RIGHT_PAREN + ; + +identifierSeq + : ident+=errorCapturingIdentifier (COMMA ident+=errorCapturingIdentifier)* + ; + +orderedIdentifierList + : LEFT_PAREN orderedIdentifier (COMMA orderedIdentifier)* RIGHT_PAREN + ; + +orderedIdentifier + : ident=errorCapturingIdentifier ordering=(ASC | DESC)? + ; + +identifierCommentList + : LEFT_PAREN identifierComment (COMMA identifierComment)* RIGHT_PAREN + ; + +identifierComment + : identifier commentSpec? + ; + +relationPrimary + : multipartIdentifier temporalClause? + sample? tableAlias #tableName + | LEFT_PAREN query RIGHT_PAREN sample? tableAlias #aliasedQuery + | LEFT_PAREN relation RIGHT_PAREN sample? tableAlias #aliasedRelation + | inlineTable #inlineTableDefault2 + | functionTable #tableValuedFunction + ; + +inlineTable + : VALUES expression (COMMA expression)* tableAlias + ; + +functionTable + : funcName=functionName LEFT_PAREN (expression (COMMA expression)*)? RIGHT_PAREN tableAlias + ; + +tableAlias + : (AS? strictIdentifier identifierList?)? + ; + +rowFormat + : ROW FORMAT SERDE name=stringLit (WITH SERDEPROPERTIES props=propertyList)? #rowFormatSerde + | ROW FORMAT DELIMITED + (FIELDS TERMINATED BY fieldsTerminatedBy=stringLit (ESCAPED BY escapedBy=stringLit)?)? + (COLLECTION ITEMS TERMINATED BY collectionItemsTerminatedBy=stringLit)? + (MAP KEYS TERMINATED BY keysTerminatedBy=stringLit)? + (LINES TERMINATED BY linesSeparatedBy=stringLit)? + (NULL DEFINED AS nullDefinedAs=stringLit)? #rowFormatDelimited + ; + +multipartIdentifierList + : multipartIdentifier (COMMA multipartIdentifier)* + ; + +multipartIdentifier + : parts+=errorCapturingIdentifier (DOT parts+=errorCapturingIdentifier)* + ; + +multipartIdentifierPropertyList + : multipartIdentifierProperty (COMMA multipartIdentifierProperty)* + ; + +multipartIdentifierProperty + : multipartIdentifier (OPTIONS options=propertyList)? + ; + +tableIdentifier + : (db=errorCapturingIdentifier DOT)? table=errorCapturingIdentifier + ; + +functionIdentifier + : (db=errorCapturingIdentifier DOT)? function=errorCapturingIdentifier + ; + +namedExpression + : expression (AS? (name=errorCapturingIdentifier | identifierList))? + ; + +namedExpressionSeq + : namedExpression (COMMA namedExpression)* + ; + +partitionFieldList + : LEFT_PAREN fields+=partitionField (COMMA fields+=partitionField)* RIGHT_PAREN + ; + +partitionField + : transform #partitionTransform + | colType #partitionColumn + ; + +transform + : qualifiedName #identityTransform + | transformName=identifier + LEFT_PAREN argument+=transformArgument (COMMA argument+=transformArgument)* RIGHT_PAREN #applyTransform + ; + +transformArgument + : qualifiedName + | constant + ; + +expression + : booleanExpression + ; + +expressionSeq + : expression (COMMA expression)* + ; + +booleanExpression + : NOT booleanExpression #logicalNot + | EXISTS LEFT_PAREN query RIGHT_PAREN #exists + | valueExpression predicate? #predicated + | left=booleanExpression operator=AND right=booleanExpression #logicalBinary + | left=booleanExpression operator=OR right=booleanExpression #logicalBinary + ; + +predicate + : NOT? kind=BETWEEN lower=valueExpression AND upper=valueExpression + | NOT? kind=IN LEFT_PAREN expression (COMMA expression)* RIGHT_PAREN + | NOT? kind=IN LEFT_PAREN query RIGHT_PAREN + | NOT? kind=RLIKE pattern=valueExpression + | NOT? kind=(LIKE | ILIKE) quantifier=(ANY | SOME | ALL) (LEFT_PAREN RIGHT_PAREN | LEFT_PAREN expression (COMMA expression)* RIGHT_PAREN) + | NOT? kind=(LIKE | ILIKE) pattern=valueExpression (ESCAPE escapeChar=stringLit)? + | IS NOT? kind=NULL + | IS NOT? kind=(TRUE | FALSE | UNKNOWN) + | IS NOT? kind=DISTINCT FROM right=valueExpression + ; + +valueExpression + : primaryExpression #valueExpressionDefault + | operator=(MINUS | PLUS | TILDE) valueExpression #arithmeticUnary + | left=valueExpression operator=(ASTERISK | SLASH | PERCENT | DIV) right=valueExpression #arithmeticBinary + | left=valueExpression operator=(PLUS | MINUS | CONCAT_PIPE) right=valueExpression #arithmeticBinary + | left=valueExpression operator=AMPERSAND right=valueExpression #arithmeticBinary + | left=valueExpression operator=HAT right=valueExpression #arithmeticBinary + | left=valueExpression operator=PIPE right=valueExpression #arithmeticBinary + | left=valueExpression comparisonOperator right=valueExpression #comparison + ; + +datetimeUnit + : YEAR | QUARTER | MONTH + | WEEK | DAY | DAYOFYEAR + | HOUR | MINUTE | SECOND | MILLISECOND | MICROSECOND + ; + +primaryExpression + : name=(CURRENT_DATE | CURRENT_TIMESTAMP | CURRENT_USER | USER) #currentLike + | name=(TIMESTAMPADD | DATEADD) LEFT_PAREN unit=datetimeUnit COMMA unitsAmount=valueExpression COMMA timestamp=valueExpression RIGHT_PAREN #timestampadd + | name=(TIMESTAMPDIFF | DATEDIFF) LEFT_PAREN unit=datetimeUnit COMMA startTimestamp=valueExpression COMMA endTimestamp=valueExpression RIGHT_PAREN #timestampdiff + | CASE whenClause+ (ELSE elseExpression=expression)? END #searchedCase + | CASE value=expression whenClause+ (ELSE elseExpression=expression)? END #simpleCase + | name=(CAST | TRY_CAST) LEFT_PAREN expression AS dataType RIGHT_PAREN #cast + | STRUCT LEFT_PAREN (argument+=namedExpression (COMMA argument+=namedExpression)*)? RIGHT_PAREN #struct + | FIRST LEFT_PAREN expression (IGNORE NULLS)? RIGHT_PAREN #first + | ANY_VALUE LEFT_PAREN expression (IGNORE NULLS)? RIGHT_PAREN #any_value + | LAST LEFT_PAREN expression (IGNORE NULLS)? RIGHT_PAREN #last + | POSITION LEFT_PAREN substr=valueExpression IN str=valueExpression RIGHT_PAREN #position + | constant #constantDefault + | ASTERISK #star + | qualifiedName DOT ASTERISK #star + | LEFT_PAREN namedExpression (COMMA namedExpression)+ RIGHT_PAREN #rowConstructor + | LEFT_PAREN query RIGHT_PAREN #subqueryExpression + | functionName LEFT_PAREN (setQuantifier? argument+=expression (COMMA argument+=expression)*)? RIGHT_PAREN + (FILTER LEFT_PAREN WHERE where=booleanExpression RIGHT_PAREN)? + (nullsOption=(IGNORE | RESPECT) NULLS)? ( OVER windowSpec)? #functionCall + | identifier ARROW expression #lambda + | LEFT_PAREN identifier (COMMA identifier)+ RIGHT_PAREN ARROW expression #lambda + | value=primaryExpression LEFT_BRACKET index=valueExpression RIGHT_BRACKET #subscript + | identifier #columnReference + | base=primaryExpression DOT fieldName=identifier #dereference + | LEFT_PAREN expression RIGHT_PAREN #parenthesizedExpression + | EXTRACT LEFT_PAREN field=identifier FROM source=valueExpression RIGHT_PAREN #extract + | (SUBSTR | SUBSTRING) LEFT_PAREN str=valueExpression (FROM | COMMA) pos=valueExpression + ((FOR | COMMA) len=valueExpression)? RIGHT_PAREN #substring + | TRIM LEFT_PAREN trimOption=(BOTH | LEADING | TRAILING)? (trimStr=valueExpression)? + FROM srcStr=valueExpression RIGHT_PAREN #trim + | OVERLAY LEFT_PAREN input=valueExpression PLACING replace=valueExpression + FROM position=valueExpression (FOR length=valueExpression)? RIGHT_PAREN #overlay + | name=(PERCENTILE_CONT | PERCENTILE_DISC) LEFT_PAREN percentage=valueExpression RIGHT_PAREN + WITHIN GROUP LEFT_PAREN ORDER BY sortItem RIGHT_PAREN + (FILTER LEFT_PAREN WHERE where=booleanExpression RIGHT_PAREN)? ( OVER windowSpec)? #percentile + ; + +constant + : NULL #nullLiteral + | COLON identifier #parameterLiteral + | interval #intervalLiteral + | identifier stringLit #typeConstructor + | number #numericLiteral + | booleanValue #booleanLiteral + | stringLit+ #stringLiteral + ; + +comparisonOperator + : EQ | NEQ | NEQJ | LT | LTE | GT | GTE | NSEQ + ; + +arithmeticOperator + : PLUS | MINUS | ASTERISK | SLASH | PERCENT | DIV | TILDE | AMPERSAND | PIPE | CONCAT_PIPE | HAT + ; + +predicateOperator + : OR | AND | IN | NOT + ; + +booleanValue + : TRUE | FALSE + ; + +interval + : INTERVAL (errorCapturingMultiUnitsInterval | errorCapturingUnitToUnitInterval) + ; + +errorCapturingMultiUnitsInterval + : body=multiUnitsInterval unitToUnitInterval? + ; + +multiUnitsInterval + : (intervalValue unit+=unitInMultiUnits)+ + ; + +errorCapturingUnitToUnitInterval + : body=unitToUnitInterval (error1=multiUnitsInterval | error2=unitToUnitInterval)? + ; + +unitToUnitInterval + : value=intervalValue from=unitInUnitToUnit TO to=unitInUnitToUnit + ; + +intervalValue + : (PLUS | MINUS)? + (INTEGER_VALUE | DECIMAL_VALUE | stringLit) + ; + +unitInMultiUnits + : NANOSECOND | NANOSECONDS | MICROSECOND | MICROSECONDS | MILLISECOND | MILLISECONDS + | SECOND | SECONDS | MINUTE | MINUTES | HOUR | HOURS | DAY | DAYS | WEEK | WEEKS + | MONTH | MONTHS | YEAR | YEARS + ; + +unitInUnitToUnit + : SECOND | MINUTE | HOUR | DAY | MONTH | YEAR + ; + +colPosition + : position=FIRST | position=AFTER afterCol=errorCapturingIdentifier + ; + +dataType + : complex=ARRAY LT dataType GT #complexDataType + | complex=MAP LT dataType COMMA dataType GT #complexDataType + | complex=STRUCT (LT complexColTypeList? GT | NEQ) #complexDataType + | INTERVAL from=(YEAR | MONTH) (TO to=MONTH)? #yearMonthIntervalDataType + | INTERVAL from=(DAY | HOUR | MINUTE | SECOND) + (TO to=(HOUR | MINUTE | SECOND))? #dayTimeIntervalDataType + | identifier (LEFT_PAREN INTEGER_VALUE + (COMMA INTEGER_VALUE)* RIGHT_PAREN)? #primitiveDataType + ; + +qualifiedColTypeWithPositionList + : qualifiedColTypeWithPosition (COMMA qualifiedColTypeWithPosition)* + ; + +qualifiedColTypeWithPosition + : name=multipartIdentifier dataType colDefinitionDescriptorWithPosition* + ; + +colDefinitionDescriptorWithPosition + : NOT NULL + | defaultExpression + | commentSpec + | colPosition + ; + +defaultExpression + : DEFAULT expression + ; + +colTypeList + : colType (COMMA colType)* + ; + +colType + : colName=errorCapturingIdentifier dataType (NOT NULL)? commentSpec? + ; + +createOrReplaceTableColTypeList + : createOrReplaceTableColType (COMMA createOrReplaceTableColType)* + ; + +createOrReplaceTableColType + : colName=errorCapturingIdentifier dataType colDefinitionOption* + ; + +colDefinitionOption + : NOT NULL + | defaultExpression + | generationExpression + | commentSpec + ; + +generationExpression + : GENERATED ALWAYS AS LEFT_PAREN expression RIGHT_PAREN + ; + +complexColTypeList + : complexColType (COMMA complexColType)* + ; + +complexColType + : identifier COLON? dataType (NOT NULL)? commentSpec? + ; + +whenClause + : WHEN condition=expression THEN result=expression + ; + +windowClause + : WINDOW namedWindow (COMMA namedWindow)* + ; + +namedWindow + : name=errorCapturingIdentifier AS windowSpec + ; + +windowSpec + : name=errorCapturingIdentifier #windowRef + | LEFT_PAREN name=errorCapturingIdentifier RIGHT_PAREN #windowRef + | LEFT_PAREN + ( CLUSTER BY partition+=expression (COMMA partition+=expression)* + | ((PARTITION | DISTRIBUTE) BY partition+=expression (COMMA partition+=expression)*)? + ((ORDER | SORT) BY sortItem (COMMA sortItem)*)?) + windowFrame? + RIGHT_PAREN #windowDef + ; + +windowFrame + : frameType=RANGE start=frameBound + | frameType=ROWS start=frameBound + | frameType=RANGE BETWEEN start=frameBound AND end=frameBound + | frameType=ROWS BETWEEN start=frameBound AND end=frameBound + ; + +frameBound + : UNBOUNDED boundType=(PRECEDING | FOLLOWING) + | boundType=CURRENT ROW + | expression boundType=(PRECEDING | FOLLOWING) + ; + +qualifiedNameList + : qualifiedName (COMMA qualifiedName)* + ; + +functionName + : qualifiedName + | FILTER + | LEFT + | RIGHT + ; + +qualifiedName + : identifier (DOT identifier)* + ; + +// this rule is used for explicitly capturing wrong identifiers such as test-table, which should actually be `test-table` +// replace identifier with errorCapturingIdentifier where the immediate follow symbol is not an expression, otherwise +// valid expressions such as "a-b" can be recognized as an identifier +errorCapturingIdentifier + : identifier errorCapturingIdentifierExtra + ; + +// extra left-factoring grammar +errorCapturingIdentifierExtra + : (MINUS identifier)+ #errorIdent + | #realIdent + ; + +identifier + : strictIdentifier + | strictNonReserved + ; + +strictIdentifier + : IDENTIFIER #unquotedIdentifier + | quotedIdentifier #quotedIdentifierAlternative + | ansiNonReserved #unquotedIdentifier + | nonReserved #unquotedIdentifier + ; + +quotedIdentifier + : BACKQUOTED_IDENTIFIER + | DOUBLEQUOTED_STRING + ; + +backQuotedIdentifier + : BACKQUOTED_IDENTIFIER + ; + +number + : MINUS? EXPONENT_VALUE #exponentLiteral + | MINUS? DECIMAL_VALUE #decimalLiteral + | MINUS? (EXPONENT_VALUE | DECIMAL_VALUE) #legacyDecimalLiteral + | MINUS? INTEGER_VALUE #integerLiteral + | MINUS? BIGINT_LITERAL #bigIntLiteral + | MINUS? SMALLINT_LITERAL #smallIntLiteral + | MINUS? TINYINT_LITERAL #tinyIntLiteral + | MINUS? DOUBLE_LITERAL #doubleLiteral + | MINUS? FLOAT_LITERAL #floatLiteral + | MINUS? BIGDECIMAL_LITERAL #bigDecimalLiteral + ; + +alterColumnAction + : TYPE dataType + | commentSpec + | colPosition + | setOrDrop=(SET | DROP) NOT NULL + | SET defaultExpression + | dropDefault=DROP DEFAULT + ; + +stringLit + : STRING + | DOUBLEQUOTED_STRING + ; + +comment + : stringLit + | NULL + ; + +version + : INTEGER_VALUE + | stringLit + ; + +// When `SQL_standard_keyword_behavior=true`, there are 2 kinds of keywords in Spark SQL. +// - Reserved keywords: +// Keywords that are reserved and can't be used as identifiers for table, view, column, +// function, alias, etc. +// - Non-reserved keywords: +// Keywords that have a special meaning only in particular contexts and can be used as +// identifiers in other contexts. For example, `EXPLAIN SELECT ...` is a command, but EXPLAIN +// can be used as identifiers in other places. +// You can find the full keywords list by searching "Start of the keywords list" in this file. +// The non-reserved keywords are listed below. Keywords not in this list are reserved keywords. +ansiNonReserved +//--ANSI-NON-RESERVED-START + : ADD + | AFTER + | ALTER + | ALWAYS + | ANALYZE + | ANTI + | ANY_VALUE + | ARCHIVE + | ARRAY + | ASC + | AT + | BETWEEN + | BUCKET + | BUCKETS + | BY + | CACHE + | CASCADE + | CATALOG + | CATALOGS + | CHANGE + | CLEAR + | CLUSTER + | CLUSTERED + | CODEGEN + | COLLECTION + | COLUMNS + | COMMENT + | COMMIT + | COMPACT + | COMPACTIONS + | COMPUTE + | CONCATENATE + | COST + | CUBE + | CURRENT + | DATA + | DATABASE + | DATABASES + | DATEADD + | DATEDIFF + | DAY + | DAYS + | DAYOFYEAR + | DBPROPERTIES + | DEFAULT + | DEFINED + | DELETE + | DELIMITED + | DESC + | DESCRIBE + | DFS + | DIRECTORIES + | DIRECTORY + | DISTRIBUTE + | DIV + | DROP + | ESCAPED + | EXCHANGE + | EXCLUDE + | EXISTS + | EXPLAIN + | EXPORT + | EXTENDED + | EXTERNAL + | EXTRACT + | FIELDS + | FILEFORMAT + | FIRST + | FOLLOWING + | FORMAT + | FORMATTED + | FUNCTION + | FUNCTIONS + | GENERATED + | GLOBAL + | GROUPING + | HOUR + | HOURS + | IF + | IGNORE + | IMPORT + | INCLUDE + | INDEX + | INDEXES + | INPATH + | INPUTFORMAT + | INSERT + | INTERVAL + | ITEMS + | KEYS + | LAST + | LAZY + | LIKE + | ILIKE + | LIMIT + | LINES + | LIST + | LOAD + | LOCAL + | LOCATION + | LOCK + | LOCKS + | LOGICAL + | MACRO + | MAP + | MATCHED + | MERGE + | MICROSECOND + | MICROSECONDS + | MILLISECOND + | MILLISECONDS + | MINUTE + | MINUTES + | MONTH + | MONTHS + | MSCK + | NAMESPACE + | NAMESPACES + | NANOSECOND + | NANOSECONDS + | NO + | NULLS + | OF + | OPTION + | OPTIONS + | OUT + | OUTPUTFORMAT + | OVER + | OVERLAY + | OVERWRITE + | PARTITION + | PARTITIONED + | PARTITIONS + | PERCENTLIT + | PIVOT + | PLACING + | POSITION + | PRECEDING + | PRINCIPALS + | PROPERTIES + | PURGE + | QUARTER + | QUERY + | RANGE + | RECORDREADER + | RECORDWRITER + | RECOVER + | REDUCE + | REFRESH + | RENAME + | REPAIR + | REPEATABLE + | REPLACE + | RESET + | RESPECT + | RESTRICT + | REVOKE + | RLIKE + | ROLE + | ROLES + | ROLLBACK + | ROLLUP + | ROW + | ROWS + | SCHEMA + | SCHEMAS + | SECOND + | SECONDS + | SEMI + | SEPARATED + | SERDE + | SERDEPROPERTIES + | SET + | SETMINUS + | SETS + | SHOW + | SKEWED + | SORT + | SORTED + | SOURCE + | START + | STATISTICS + | STORED + | STRATIFY + | STRUCT + | SUBSTR + | SUBSTRING + | SYNC + | SYSTEM_TIME + | SYSTEM_VERSION + | TABLES + | TABLESAMPLE + | TARGET + | TBLPROPERTIES + | TEMPORARY + | TERMINATED + | TIMESTAMP + | TIMESTAMPADD + | TIMESTAMPDIFF + | TOUCH + | TRANSACTION + | TRANSACTIONS + | TRANSFORM + | TRIM + | TRUE + | TRUNCATE + | TRY_CAST + | TYPE + | UNARCHIVE + | UNBOUNDED + | UNCACHE + | UNLOCK + | UNPIVOT + | UNSET + | UPDATE + | USE + | VALUES + | VERSION + | VIEW + | VIEWS + | WEEK + | WEEKS + | WINDOW + | YEAR + | YEARS + | ZONE +//--ANSI-NON-RESERVED-END + ; + +// When `SQL_standard_keyword_behavior=false`, there are 2 kinds of keywords in Spark SQL. +// - Non-reserved keywords: +// Same definition as the one when `SQL_standard_keyword_behavior=true`. +// - Strict-non-reserved keywords: +// A strict version of non-reserved keywords, which can not be used as table alias. +// You can find the full keywords list by searching "Start of the keywords list" in this file. +// The strict-non-reserved keywords are listed in `strictNonReserved`. +// The non-reserved keywords are listed in `nonReserved`. +// These 2 together contain all the keywords. +strictNonReserved + : ANTI + | CROSS + | EXCEPT + | FULL + | INNER + | INTERSECT + | JOIN + | LATERAL + | LEFT + | NATURAL + | ON + | RIGHT + | SEMI + | SETMINUS + | UNION + | USING + ; + +nonReserved +//--DEFAULT-NON-RESERVED-START + : ADD + | AFTER + | ALL + | ALTER + | ALWAYS + | ANALYZE + | AND + | ANY + | ANY_VALUE + | ARCHIVE + | ARRAY + | AS + | ASC + | AT + | AUTHORIZATION + | BETWEEN + | BOTH + | BUCKET + | BUCKETS + | BY + | CACHE + | CASCADE + | CASE + | CAST + | CATALOG + | CATALOGS + | CHANGE + | CHECK + | CLEAR + | CLUSTER + | CLUSTERED + | CODEGEN + | COLLATE + | COLLECTION + | COLUMN + | COLUMNS + | COMMENT + | COMMIT + | COMPACT + | COMPACTIONS + | COMPUTE + | CONCATENATE + | CONSTRAINT + | COST + | CREATE + | CUBE + | CURRENT + | CURRENT_DATE + | CURRENT_TIME + | CURRENT_TIMESTAMP + | CURRENT_USER + | DATA + | DATABASE + | DATABASES + | DATEADD + | DATEDIFF + | DAY + | DAYS + | DAYOFYEAR + | DBPROPERTIES + | DEFAULT + | DEFINED + | DELETE + | DELIMITED + | DESC + | DESCRIBE + | DFS + | DIRECTORIES + | DIRECTORY + | DISTINCT + | DISTRIBUTE + | DIV + | DROP + | ELSE + | END + | ESCAPE + | ESCAPED + | EXCHANGE + | EXCLUDE + | EXISTS + | EXPLAIN + | EXPORT + | EXTENDED + | EXTERNAL + | EXTRACT + | FALSE + | FETCH + | FILTER + | FIELDS + | FILEFORMAT + | FIRST + | FOLLOWING + | FOR + | FOREIGN + | FORMAT + | FORMATTED + | FROM + | FUNCTION + | FUNCTIONS + | GENERATED + | GLOBAL + | GRANT + | GROUP + | GROUPING + | HAVING + | HOUR + | HOURS + | IF + | IGNORE + | IMPORT + | IN + | INCLUDE + | INDEX + | INDEXES + | INPATH + | INPUTFORMAT + | INSERT + | INTERVAL + | INTO + | IS + | ITEMS + | KEYS + | LAST + | LAZY + | LEADING + | LIKE + | ILIKE + | LIMIT + | LINES + | LIST + | LOAD + | LOCAL + | LOCATION + | LOCK + | LOCKS + | LOGICAL + | MACRO + | MAP + | MATCHED + | MERGE + | MICROSECOND + | MICROSECONDS + | MILLISECOND + | MILLISECONDS + | MINUTE + | MINUTES + | MONTH + | MONTHS + | MSCK + | NAMESPACE + | NAMESPACES + | NANOSECOND + | NANOSECONDS + | NO + | NOT + | NULL + | NULLS + | OF + | OFFSET + | ONLY + | OPTION + | OPTIONS + | OR + | ORDER + | OUT + | OUTER + | OUTPUTFORMAT + | OVER + | OVERLAPS + | OVERLAY + | OVERWRITE + | PARTITION + | PARTITIONED + | PARTITIONS + | PERCENTILE_CONT + | PERCENTILE_DISC + | PERCENTLIT + | PIVOT + | PLACING + | POSITION + | PRECEDING + | PRIMARY + | PRINCIPALS + | PROPERTIES + | PURGE + | QUARTER + | QUERY + | RANGE + | RECORDREADER + | RECORDWRITER + | RECOVER + | REDUCE + | REFERENCES + | REFRESH + | RENAME + | REPAIR + | REPEATABLE + | REPLACE + | RESET + | RESPECT + | RESTRICT + | REVOKE + | RLIKE + | ROLE + | ROLES + | ROLLBACK + | ROLLUP + | ROW + | ROWS + | SCHEMA + | SCHEMAS + | SECOND + | SECONDS + | SELECT + | SEPARATED + | SERDE + | SERDEPROPERTIES + | SESSION_USER + | SET + | SETS + | SHOW + | SKEWED + | SOME + | SORT + | SORTED + | SOURCE + | START + | STATISTICS + | STORED + | STRATIFY + | STRUCT + | SUBSTR + | SUBSTRING + | SYNC + | SYSTEM_TIME + | SYSTEM_VERSION + | TABLE + | TABLES + | TABLESAMPLE + | TARGET + | TBLPROPERTIES + | TEMPORARY + | TERMINATED + | THEN + | TIME + | TIMESTAMP + | TIMESTAMPADD + | TIMESTAMPDIFF + | TO + | TOUCH + | TRAILING + | TRANSACTION + | TRANSACTIONS + | TRANSFORM + | TRIM + | TRUE + | TRUNCATE + | TRY_CAST + | TYPE + | UNARCHIVE + | UNBOUNDED + | UNCACHE + | UNIQUE + | UNKNOWN + | UNLOCK + | UNPIVOT + | UNSET + | UPDATE + | USE + | USER + | VALUES + | VERSION + | VIEW + | VIEWS + | WEEK + | WEEKS + | WHEN + | WHERE + | WINDOW + | WITH + | WITHIN + | YEAR + | YEARS + | ZONE +//--DEFAULT-NON-RESERVED-END + ; diff --git a/datajunction-server/datajunction_server/sql/parsing/backends/grammar/__init__.py b/datajunction-server/datajunction_server/sql/parsing/backends/grammar/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/datajunction-server/datajunction_server/sql/parsing/backends/grammar/generated/SqlBaseLexer.interp b/datajunction-server/datajunction_server/sql/parsing/backends/grammar/generated/SqlBaseLexer.interp new file mode 100644 index 000000000..64817ea0a --- /dev/null +++ b/datajunction-server/datajunction_server/sql/parsing/backends/grammar/generated/SqlBaseLexer.interp @@ -0,0 +1,1059 @@ +token literal names: +null +';' +'(' +')' +',' +'.' +'[' +']' +'ADD' +'AFTER' +'ALL' +'ALTER' +'ALWAYS' +'ANALYZE' +'AND' +'ANTI' +'ANY' +'ANY_VALUE' +'ARCHIVE' +'ARRAY' +'AS' +'ASC' +'AT' +'AUTHORIZATION' +'BETWEEN' +'BOTH' +'BUCKET' +'BUCKETS' +'BY' +'CACHE' +'CASCADE' +'CASE' +'CAST' +'CATALOG' +'CATALOGS' +'CHANGE' +'CHECK' +'CLEAR' +'CLUSTER' +'CLUSTERED' +'CODEGEN' +'COLLATE' +'COLLECTION' +'COLUMN' +'COLUMNS' +'COMMENT' +'COMMIT' +'COMPACT' +'COMPACTIONS' +'COMPUTE' +'CONCATENATE' +'CONSTRAINT' +'COST' +'CREATE' +'CROSS' +'CUBE' +'CURRENT' +'CURRENT_DATE' +'CURRENT_TIME' +'CURRENT_TIMESTAMP' +'CURRENT_USER' +'DAY' +'DAYS' +'DAYOFYEAR' +'DATA' +'DATABASE' +'DATABASES' +'DATEADD' +'DATEDIFF' +'DBPROPERTIES' +'DEFAULT' +'DEFINED' +'DELETE' +'DELIMITED' +'DESC' +'DESCRIBE' +'DFS' +'DIRECTORIES' +'DIRECTORY' +'DISTINCT' +'DISTRIBUTE' +'DIV' +'DROP' +'ELSE' +'END' +'ESCAPE' +'ESCAPED' +'EXCEPT' +'EXCHANGE' +'EXCLUDE' +'EXISTS' +'EXPLAIN' +'EXPORT' +'EXTENDED' +'EXTERNAL' +'EXTRACT' +'FALSE' +'FETCH' +'FIELDS' +'FILTER' +'FILEFORMAT' +'FIRST' +'FOLLOWING' +'FOR' +'FOREIGN' +'FORMAT' +'FORMATTED' +'FROM' +'FULL' +'FUNCTION' +'FUNCTIONS' +'GENERATED' +'GLOBAL' +'GRANT' +'GROUP' +'GROUPING' +'HAVING' +'HOUR' +'HOURS' +'IF' +'IGNORE' +'IMPORT' +'IN' +'INCLUDE' +'INDEX' +'INDEXES' +'INNER' +'INPATH' +'INPUTFORMAT' +'INSERT' +'INTERSECT' +'INTERVAL' +'INTO' +'IS' +'ITEMS' +'JOIN' +'KEYS' +'LAST' +'LATERAL' +'LAZY' +'LEADING' +'LEFT' +'LIKE' +'ILIKE' +'LIMIT' +'LINES' +'LIST' +'LOAD' +'LOCAL' +'LOCATION' +'LOCK' +'LOCKS' +'LOGICAL' +'MACRO' +'MAP' +'MATCHED' +'MERGE' +'MICROSECOND' +'MICROSECONDS' +'MILLISECOND' +'MILLISECONDS' +'MINUTE' +'MINUTES' +'MONTH' +'MONTHS' +'MSCK' +'NAMESPACE' +'NAMESPACES' +'NANOSECOND' +'NANOSECONDS' +'NATURAL' +'NO' +null +'NULL' +'NULLS' +'OF' +'OFFSET' +'ON' +'ONLY' +'OPTION' +'OPTIONS' +'OR' +'ORDER' +'OUT' +'OUTER' +'OUTPUTFORMAT' +'OVER' +'OVERLAPS' +'OVERLAY' +'OVERWRITE' +'PARTITION' +'PARTITIONED' +'PARTITIONS' +'PERCENTILE_CONT' +'PERCENTILE_DISC' +'PERCENT' +'PIVOT' +'PLACING' +'POSITION' +'PRECEDING' +'PRIMARY' +'PRINCIPALS' +'PROPERTIES' +'PURGE' +'QUARTER' +'QUERY' +'RANGE' +'RECORDREADER' +'RECORDWRITER' +'RECOVER' +'REDUCE' +'REFERENCES' +'REFRESH' +'RENAME' +'REPAIR' +'REPEATABLE' +'REPLACE' +'RESET' +'RESPECT' +'RESTRICT' +'REVOKE' +'RIGHT' +null +'ROLE' +'ROLES' +'ROLLBACK' +'ROLLUP' +'ROW' +'ROWS' +'SECOND' +'SECONDS' +'SCHEMA' +'SCHEMAS' +'SELECT' +'SEMI' +'SEPARATED' +'SERDE' +'SERDEPROPERTIES' +'SESSION_USER' +'SET' +'MINUS' +'SETS' +'SHOW' +'SKEWED' +'SOME' +'SORT' +'SORTED' +'SOURCE' +'START' +'STATISTICS' +'STORED' +'STRATIFY' +'STRUCT' +'SUBSTR' +'SUBSTRING' +'SYNC' +'SYSTEM_TIME' +'SYSTEM_VERSION' +'TABLE' +'TABLES' +'TABLESAMPLE' +'TARGET' +'TBLPROPERTIES' +null +'TERMINATED' +'THEN' +'TIME' +'TIMESTAMP' +'TIMESTAMPADD' +'TIMESTAMPDIFF' +'TO' +'TOUCH' +'TRAILING' +'TRANSACTION' +'TRANSACTIONS' +'TRANSFORM' +'TRIM' +'TRUE' +'TRUNCATE' +'TRY_CAST' +'TYPE' +'UNARCHIVE' +'UNBOUNDED' +'UNCACHE' +'UNION' +'UNIQUE' +'UNKNOWN' +'UNLOCK' +'UNPIVOT' +'UNSET' +'UPDATE' +'USE' +'USER' +'USING' +'VALUES' +'VERSION' +'VIEW' +'VIEWS' +'WEEK' +'WEEKS' +'WHEN' +'WHERE' +'WINDOW' +'WITH' +'WITHIN' +'YEAR' +'YEARS' +'ZONE' +null +'<=>' +'<>' +'!=' +'<' +null +'>' +null +'+' +'-' +'*' +'/' +'%' +'~' +'&' +'|' +'||' +'^' +':' +'->' +'/*+' +'*/' +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null + +token symbolic names: +null +SEMICOLON +LEFT_PAREN +RIGHT_PAREN +COMMA +DOT +LEFT_BRACKET +RIGHT_BRACKET +ADD +AFTER +ALL +ALTER +ALWAYS +ANALYZE +AND +ANTI +ANY +ANY_VALUE +ARCHIVE +ARRAY +AS +ASC +AT +AUTHORIZATION +BETWEEN +BOTH +BUCKET +BUCKETS +BY +CACHE +CASCADE +CASE +CAST +CATALOG +CATALOGS +CHANGE +CHECK +CLEAR +CLUSTER +CLUSTERED +CODEGEN +COLLATE +COLLECTION +COLUMN +COLUMNS +COMMENT +COMMIT +COMPACT +COMPACTIONS +COMPUTE +CONCATENATE +CONSTRAINT +COST +CREATE +CROSS +CUBE +CURRENT +CURRENT_DATE +CURRENT_TIME +CURRENT_TIMESTAMP +CURRENT_USER +DAY +DAYS +DAYOFYEAR +DATA +DATABASE +DATABASES +DATEADD +DATEDIFF +DBPROPERTIES +DEFAULT +DEFINED +DELETE +DELIMITED +DESC +DESCRIBE +DFS +DIRECTORIES +DIRECTORY +DISTINCT +DISTRIBUTE +DIV +DROP +ELSE +END +ESCAPE +ESCAPED +EXCEPT +EXCHANGE +EXCLUDE +EXISTS +EXPLAIN +EXPORT +EXTENDED +EXTERNAL +EXTRACT +FALSE +FETCH +FIELDS +FILTER +FILEFORMAT +FIRST +FOLLOWING +FOR +FOREIGN +FORMAT +FORMATTED +FROM +FULL +FUNCTION +FUNCTIONS +GENERATED +GLOBAL +GRANT +GROUP +GROUPING +HAVING +HOUR +HOURS +IF +IGNORE +IMPORT +IN +INCLUDE +INDEX +INDEXES +INNER +INPATH +INPUTFORMAT +INSERT +INTERSECT +INTERVAL +INTO +IS +ITEMS +JOIN +KEYS +LAST +LATERAL +LAZY +LEADING +LEFT +LIKE +ILIKE +LIMIT +LINES +LIST +LOAD +LOCAL +LOCATION +LOCK +LOCKS +LOGICAL +MACRO +MAP +MATCHED +MERGE +MICROSECOND +MICROSECONDS +MILLISECOND +MILLISECONDS +MINUTE +MINUTES +MONTH +MONTHS +MSCK +NAMESPACE +NAMESPACES +NANOSECOND +NANOSECONDS +NATURAL +NO +NOT +NULL +NULLS +OF +OFFSET +ON +ONLY +OPTION +OPTIONS +OR +ORDER +OUT +OUTER +OUTPUTFORMAT +OVER +OVERLAPS +OVERLAY +OVERWRITE +PARTITION +PARTITIONED +PARTITIONS +PERCENTILE_CONT +PERCENTILE_DISC +PERCENTLIT +PIVOT +PLACING +POSITION +PRECEDING +PRIMARY +PRINCIPALS +PROPERTIES +PURGE +QUARTER +QUERY +RANGE +RECORDREADER +RECORDWRITER +RECOVER +REDUCE +REFERENCES +REFRESH +RENAME +REPAIR +REPEATABLE +REPLACE +RESET +RESPECT +RESTRICT +REVOKE +RIGHT +RLIKE +ROLE +ROLES +ROLLBACK +ROLLUP +ROW +ROWS +SECOND +SECONDS +SCHEMA +SCHEMAS +SELECT +SEMI +SEPARATED +SERDE +SERDEPROPERTIES +SESSION_USER +SET +SETMINUS +SETS +SHOW +SKEWED +SOME +SORT +SORTED +SOURCE +START +STATISTICS +STORED +STRATIFY +STRUCT +SUBSTR +SUBSTRING +SYNC +SYSTEM_TIME +SYSTEM_VERSION +TABLE +TABLES +TABLESAMPLE +TARGET +TBLPROPERTIES +TEMPORARY +TERMINATED +THEN +TIME +TIMESTAMP +TIMESTAMPADD +TIMESTAMPDIFF +TO +TOUCH +TRAILING +TRANSACTION +TRANSACTIONS +TRANSFORM +TRIM +TRUE +TRUNCATE +TRY_CAST +TYPE +UNARCHIVE +UNBOUNDED +UNCACHE +UNION +UNIQUE +UNKNOWN +UNLOCK +UNPIVOT +UNSET +UPDATE +USE +USER +USING +VALUES +VERSION +VIEW +VIEWS +WEEK +WEEKS +WHEN +WHERE +WINDOW +WITH +WITHIN +YEAR +YEARS +ZONE +EQ +NSEQ +NEQ +NEQJ +LT +LTE +GT +GTE +PLUS +MINUS +ASTERISK +SLASH +PERCENT +TILDE +AMPERSAND +PIPE +CONCAT_PIPE +HAT +COLON +ARROW +HENT_START +HENT_END +STRING +DOUBLEQUOTED_STRING +BIGINT_LITERAL +SMALLINT_LITERAL +TINYINT_LITERAL +INTEGER_VALUE +EXPONENT_VALUE +DECIMAL_VALUE +FLOAT_LITERAL +DOUBLE_LITERAL +BIGDECIMAL_LITERAL +IDENTIFIER +BACKQUOTED_IDENTIFIER +SIMPLE_COMMENT +BRACKETED_COMMENT +WS +UNRECOGNIZED + +rule names: +SEMICOLON +LEFT_PAREN +RIGHT_PAREN +COMMA +DOT +LEFT_BRACKET +RIGHT_BRACKET +ADD +AFTER +ALL +ALTER +ALWAYS +ANALYZE +AND +ANTI +ANY +ANY_VALUE +ARCHIVE +ARRAY +AS +ASC +AT +AUTHORIZATION +BETWEEN +BOTH +BUCKET +BUCKETS +BY +CACHE +CASCADE +CASE +CAST +CATALOG +CATALOGS +CHANGE +CHECK +CLEAR +CLUSTER +CLUSTERED +CODEGEN +COLLATE +COLLECTION +COLUMN +COLUMNS +COMMENT +COMMIT +COMPACT +COMPACTIONS +COMPUTE +CONCATENATE +CONSTRAINT +COST +CREATE +CROSS +CUBE +CURRENT +CURRENT_DATE +CURRENT_TIME +CURRENT_TIMESTAMP +CURRENT_USER +DAY +DAYS +DAYOFYEAR +DATA +DATABASE +DATABASES +DATEADD +DATEDIFF +DBPROPERTIES +DEFAULT +DEFINED +DELETE +DELIMITED +DESC +DESCRIBE +DFS +DIRECTORIES +DIRECTORY +DISTINCT +DISTRIBUTE +DIV +DROP +ELSE +END +ESCAPE +ESCAPED +EXCEPT +EXCHANGE +EXCLUDE +EXISTS +EXPLAIN +EXPORT +EXTENDED +EXTERNAL +EXTRACT +FALSE +FETCH +FIELDS +FILTER +FILEFORMAT +FIRST +FOLLOWING +FOR +FOREIGN +FORMAT +FORMATTED +FROM +FULL +FUNCTION +FUNCTIONS +GENERATED +GLOBAL +GRANT +GROUP +GROUPING +HAVING +HOUR +HOURS +IF +IGNORE +IMPORT +IN +INCLUDE +INDEX +INDEXES +INNER +INPATH +INPUTFORMAT +INSERT +INTERSECT +INTERVAL +INTO +IS +ITEMS +JOIN +KEYS +LAST +LATERAL +LAZY +LEADING +LEFT +LIKE +ILIKE +LIMIT +LINES +LIST +LOAD +LOCAL +LOCATION +LOCK +LOCKS +LOGICAL +MACRO +MAP +MATCHED +MERGE +MICROSECOND +MICROSECONDS +MILLISECOND +MILLISECONDS +MINUTE +MINUTES +MONTH +MONTHS +MSCK +NAMESPACE +NAMESPACES +NANOSECOND +NANOSECONDS +NATURAL +NO +NOT +NULL +NULLS +OF +OFFSET +ON +ONLY +OPTION +OPTIONS +OR +ORDER +OUT +OUTER +OUTPUTFORMAT +OVER +OVERLAPS +OVERLAY +OVERWRITE +PARTITION +PARTITIONED +PARTITIONS +PERCENTILE_CONT +PERCENTILE_DISC +PERCENTLIT +PIVOT +PLACING +POSITION +PRECEDING +PRIMARY +PRINCIPALS +PROPERTIES +PURGE +QUARTER +QUERY +RANGE +RECORDREADER +RECORDWRITER +RECOVER +REDUCE +REFERENCES +REFRESH +RENAME +REPAIR +REPEATABLE +REPLACE +RESET +RESPECT +RESTRICT +REVOKE +RIGHT +RLIKE +ROLE +ROLES +ROLLBACK +ROLLUP +ROW +ROWS +SECOND +SECONDS +SCHEMA +SCHEMAS +SELECT +SEMI +SEPARATED +SERDE +SERDEPROPERTIES +SESSION_USER +SET +SETMINUS +SETS +SHOW +SKEWED +SOME +SORT +SORTED +SOURCE +START +STATISTICS +STORED +STRATIFY +STRUCT +SUBSTR +SUBSTRING +SYNC +SYSTEM_TIME +SYSTEM_VERSION +TABLE +TABLES +TABLESAMPLE +TARGET +TBLPROPERTIES +TEMPORARY +TERMINATED +THEN +TIME +TIMESTAMP +TIMESTAMPADD +TIMESTAMPDIFF +TO +TOUCH +TRAILING +TRANSACTION +TRANSACTIONS +TRANSFORM +TRIM +TRUE +TRUNCATE +TRY_CAST +TYPE +UNARCHIVE +UNBOUNDED +UNCACHE +UNION +UNIQUE +UNKNOWN +UNLOCK +UNPIVOT +UNSET +UPDATE +USE +USER +USING +VALUES +VERSION +VIEW +VIEWS +WEEK +WEEKS +WHEN +WHERE +WINDOW +WITH +WITHIN +YEAR +YEARS +ZONE +EQ +NSEQ +NEQ +NEQJ +LT +LTE +GT +GTE +PLUS +MINUS +ASTERISK +SLASH +PERCENT +TILDE +AMPERSAND +PIPE +CONCAT_PIPE +HAT +COLON +ARROW +HENT_START +HENT_END +STRING +DOUBLEQUOTED_STRING +BIGINT_LITERAL +SMALLINT_LITERAL +TINYINT_LITERAL +INTEGER_VALUE +EXPONENT_VALUE +DECIMAL_VALUE +FLOAT_LITERAL +DOUBLE_LITERAL +BIGDECIMAL_LITERAL +IDENTIFIER +BACKQUOTED_IDENTIFIER +DECIMAL_DIGITS +EXPONENT +DIGIT +LETTER +SIMPLE_COMMENT +BRACKETED_COMMENT +WS +UNRECOGNIZED + +channel names: +DEFAULT_TOKEN_CHANNEL +HIDDEN + +mode names: +DEFAULT_MODE + +atn: +[4, 0, 346, 3292, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 2, 62, 7, 62, 2, 63, 7, 63, 2, 64, 7, 64, 2, 65, 7, 65, 2, 66, 7, 66, 2, 67, 7, 67, 2, 68, 7, 68, 2, 69, 7, 69, 2, 70, 7, 70, 2, 71, 7, 71, 2, 72, 7, 72, 2, 73, 7, 73, 2, 74, 7, 74, 2, 75, 7, 75, 2, 76, 7, 76, 2, 77, 7, 77, 2, 78, 7, 78, 2, 79, 7, 79, 2, 80, 7, 80, 2, 81, 7, 81, 2, 82, 7, 82, 2, 83, 7, 83, 2, 84, 7, 84, 2, 85, 7, 85, 2, 86, 7, 86, 2, 87, 7, 87, 2, 88, 7, 88, 2, 89, 7, 89, 2, 90, 7, 90, 2, 91, 7, 91, 2, 92, 7, 92, 2, 93, 7, 93, 2, 94, 7, 94, 2, 95, 7, 95, 2, 96, 7, 96, 2, 97, 7, 97, 2, 98, 7, 98, 2, 99, 7, 99, 2, 100, 7, 100, 2, 101, 7, 101, 2, 102, 7, 102, 2, 103, 7, 103, 2, 104, 7, 104, 2, 105, 7, 105, 2, 106, 7, 106, 2, 107, 7, 107, 2, 108, 7, 108, 2, 109, 7, 109, 2, 110, 7, 110, 2, 111, 7, 111, 2, 112, 7, 112, 2, 113, 7, 113, 2, 114, 7, 114, 2, 115, 7, 115, 2, 116, 7, 116, 2, 117, 7, 117, 2, 118, 7, 118, 2, 119, 7, 119, 2, 120, 7, 120, 2, 121, 7, 121, 2, 122, 7, 122, 2, 123, 7, 123, 2, 124, 7, 124, 2, 125, 7, 125, 2, 126, 7, 126, 2, 127, 7, 127, 2, 128, 7, 128, 2, 129, 7, 129, 2, 130, 7, 130, 2, 131, 7, 131, 2, 132, 7, 132, 2, 133, 7, 133, 2, 134, 7, 134, 2, 135, 7, 135, 2, 136, 7, 136, 2, 137, 7, 137, 2, 138, 7, 138, 2, 139, 7, 139, 2, 140, 7, 140, 2, 141, 7, 141, 2, 142, 7, 142, 2, 143, 7, 143, 2, 144, 7, 144, 2, 145, 7, 145, 2, 146, 7, 146, 2, 147, 7, 147, 2, 148, 7, 148, 2, 149, 7, 149, 2, 150, 7, 150, 2, 151, 7, 151, 2, 152, 7, 152, 2, 153, 7, 153, 2, 154, 7, 154, 2, 155, 7, 155, 2, 156, 7, 156, 2, 157, 7, 157, 2, 158, 7, 158, 2, 159, 7, 159, 2, 160, 7, 160, 2, 161, 7, 161, 2, 162, 7, 162, 2, 163, 7, 163, 2, 164, 7, 164, 2, 165, 7, 165, 2, 166, 7, 166, 2, 167, 7, 167, 2, 168, 7, 168, 2, 169, 7, 169, 2, 170, 7, 170, 2, 171, 7, 171, 2, 172, 7, 172, 2, 173, 7, 173, 2, 174, 7, 174, 2, 175, 7, 175, 2, 176, 7, 176, 2, 177, 7, 177, 2, 178, 7, 178, 2, 179, 7, 179, 2, 180, 7, 180, 2, 181, 7, 181, 2, 182, 7, 182, 2, 183, 7, 183, 2, 184, 7, 184, 2, 185, 7, 185, 2, 186, 7, 186, 2, 187, 7, 187, 2, 188, 7, 188, 2, 189, 7, 189, 2, 190, 7, 190, 2, 191, 7, 191, 2, 192, 7, 192, 2, 193, 7, 193, 2, 194, 7, 194, 2, 195, 7, 195, 2, 196, 7, 196, 2, 197, 7, 197, 2, 198, 7, 198, 2, 199, 7, 199, 2, 200, 7, 200, 2, 201, 7, 201, 2, 202, 7, 202, 2, 203, 7, 203, 2, 204, 7, 204, 2, 205, 7, 205, 2, 206, 7, 206, 2, 207, 7, 207, 2, 208, 7, 208, 2, 209, 7, 209, 2, 210, 7, 210, 2, 211, 7, 211, 2, 212, 7, 212, 2, 213, 7, 213, 2, 214, 7, 214, 2, 215, 7, 215, 2, 216, 7, 216, 2, 217, 7, 217, 2, 218, 7, 218, 2, 219, 7, 219, 2, 220, 7, 220, 2, 221, 7, 221, 2, 222, 7, 222, 2, 223, 7, 223, 2, 224, 7, 224, 2, 225, 7, 225, 2, 226, 7, 226, 2, 227, 7, 227, 2, 228, 7, 228, 2, 229, 7, 229, 2, 230, 7, 230, 2, 231, 7, 231, 2, 232, 7, 232, 2, 233, 7, 233, 2, 234, 7, 234, 2, 235, 7, 235, 2, 236, 7, 236, 2, 237, 7, 237, 2, 238, 7, 238, 2, 239, 7, 239, 2, 240, 7, 240, 2, 241, 7, 241, 2, 242, 7, 242, 2, 243, 7, 243, 2, 244, 7, 244, 2, 245, 7, 245, 2, 246, 7, 246, 2, 247, 7, 247, 2, 248, 7, 248, 2, 249, 7, 249, 2, 250, 7, 250, 2, 251, 7, 251, 2, 252, 7, 252, 2, 253, 7, 253, 2, 254, 7, 254, 2, 255, 7, 255, 2, 256, 7, 256, 2, 257, 7, 257, 2, 258, 7, 258, 2, 259, 7, 259, 2, 260, 7, 260, 2, 261, 7, 261, 2, 262, 7, 262, 2, 263, 7, 263, 2, 264, 7, 264, 2, 265, 7, 265, 2, 266, 7, 266, 2, 267, 7, 267, 2, 268, 7, 268, 2, 269, 7, 269, 2, 270, 7, 270, 2, 271, 7, 271, 2, 272, 7, 272, 2, 273, 7, 273, 2, 274, 7, 274, 2, 275, 7, 275, 2, 276, 7, 276, 2, 277, 7, 277, 2, 278, 7, 278, 2, 279, 7, 279, 2, 280, 7, 280, 2, 281, 7, 281, 2, 282, 7, 282, 2, 283, 7, 283, 2, 284, 7, 284, 2, 285, 7, 285, 2, 286, 7, 286, 2, 287, 7, 287, 2, 288, 7, 288, 2, 289, 7, 289, 2, 290, 7, 290, 2, 291, 7, 291, 2, 292, 7, 292, 2, 293, 7, 293, 2, 294, 7, 294, 2, 295, 7, 295, 2, 296, 7, 296, 2, 297, 7, 297, 2, 298, 7, 298, 2, 299, 7, 299, 2, 300, 7, 300, 2, 301, 7, 301, 2, 302, 7, 302, 2, 303, 7, 303, 2, 304, 7, 304, 2, 305, 7, 305, 2, 306, 7, 306, 2, 307, 7, 307, 2, 308, 7, 308, 2, 309, 7, 309, 2, 310, 7, 310, 2, 311, 7, 311, 2, 312, 7, 312, 2, 313, 7, 313, 2, 314, 7, 314, 2, 315, 7, 315, 2, 316, 7, 316, 2, 317, 7, 317, 2, 318, 7, 318, 2, 319, 7, 319, 2, 320, 7, 320, 2, 321, 7, 321, 2, 322, 7, 322, 2, 323, 7, 323, 2, 324, 7, 324, 2, 325, 7, 325, 2, 326, 7, 326, 2, 327, 7, 327, 2, 328, 7, 328, 2, 329, 7, 329, 2, 330, 7, 330, 2, 331, 7, 331, 2, 332, 7, 332, 2, 333, 7, 333, 2, 334, 7, 334, 2, 335, 7, 335, 2, 336, 7, 336, 2, 337, 7, 337, 2, 338, 7, 338, 2, 339, 7, 339, 2, 340, 7, 340, 2, 341, 7, 341, 2, 342, 7, 342, 2, 343, 7, 343, 2, 344, 7, 344, 2, 345, 7, 345, 2, 346, 7, 346, 2, 347, 7, 347, 2, 348, 7, 348, 2, 349, 7, 349, 1, 0, 1, 0, 1, 1, 1, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4, 1, 5, 1, 5, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 19, 1, 19, 1, 19, 1, 20, 1, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 39, 1, 39, 1, 39, 1, 39, 1, 39, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 42, 1, 42, 1, 42, 1, 42, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 43, 1, 43, 1, 43, 1, 43, 1, 43, 1, 43, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 45, 1, 45, 1, 45, 1, 45, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 1, 46, 1, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 52, 1, 52, 1, 52, 1, 52, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 60, 1, 60, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 69, 1, 69, 1, 69, 1, 69, 1, 69, 1, 69, 1, 69, 1, 69, 1, 70, 1, 70, 1, 70, 1, 70, 1, 70, 1, 70, 1, 70, 1, 70, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 74, 1, 74, 1, 74, 1, 74, 1, 74, 1, 74, 1, 74, 1, 74, 1, 74, 1, 75, 1, 75, 1, 75, 1, 75, 1, 76, 1, 76, 1, 76, 1, 76, 1, 76, 1, 76, 1, 76, 1, 76, 1, 76, 1, 76, 1, 76, 1, 76, 1, 77, 1, 77, 1, 77, 1, 77, 1, 77, 1, 77, 1, 77, 1, 77, 1, 77, 1, 77, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 79, 1, 79, 1, 79, 1, 79, 1, 79, 1, 79, 1, 79, 1, 79, 1, 79, 1, 79, 1, 79, 1, 80, 1, 80, 1, 80, 1, 80, 1, 81, 1, 81, 1, 81, 1, 81, 1, 81, 1, 82, 1, 82, 1, 82, 1, 82, 1, 82, 1, 83, 1, 83, 1, 83, 1, 83, 1, 84, 1, 84, 1, 84, 1, 84, 1, 84, 1, 84, 1, 84, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 86, 1, 86, 1, 86, 1, 86, 1, 86, 1, 86, 1, 86, 1, 87, 1, 87, 1, 87, 1, 87, 1, 87, 1, 87, 1, 87, 1, 87, 1, 87, 1, 88, 1, 88, 1, 88, 1, 88, 1, 88, 1, 88, 1, 88, 1, 88, 1, 89, 1, 89, 1, 89, 1, 89, 1, 89, 1, 89, 1, 89, 1, 90, 1, 90, 1, 90, 1, 90, 1, 90, 1, 90, 1, 90, 1, 90, 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 1, 92, 1, 92, 1, 92, 1, 92, 1, 92, 1, 92, 1, 92, 1, 92, 1, 92, 1, 93, 1, 93, 1, 93, 1, 93, 1, 93, 1, 93, 1, 93, 1, 93, 1, 93, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 95, 1, 95, 1, 95, 1, 95, 1, 95, 1, 95, 1, 96, 1, 96, 1, 96, 1, 96, 1, 96, 1, 96, 1, 97, 1, 97, 1, 97, 1, 97, 1, 97, 1, 97, 1, 97, 1, 98, 1, 98, 1, 98, 1, 98, 1, 98, 1, 98, 1, 98, 1, 99, 1, 99, 1, 99, 1, 99, 1, 99, 1, 99, 1, 99, 1, 99, 1, 99, 1, 99, 1, 99, 1, 100, 1, 100, 1, 100, 1, 100, 1, 100, 1, 100, 1, 101, 1, 101, 1, 101, 1, 101, 1, 101, 1, 101, 1, 101, 1, 101, 1, 101, 1, 101, 1, 102, 1, 102, 1, 102, 1, 102, 1, 103, 1, 103, 1, 103, 1, 103, 1, 103, 1, 103, 1, 103, 1, 103, 1, 104, 1, 104, 1, 104, 1, 104, 1, 104, 1, 104, 1, 104, 1, 105, 1, 105, 1, 105, 1, 105, 1, 105, 1, 105, 1, 105, 1, 105, 1, 105, 1, 105, 1, 106, 1, 106, 1, 106, 1, 106, 1, 106, 1, 107, 1, 107, 1, 107, 1, 107, 1, 107, 1, 108, 1, 108, 1, 108, 1, 108, 1, 108, 1, 108, 1, 108, 1, 108, 1, 108, 1, 109, 1, 109, 1, 109, 1, 109, 1, 109, 1, 109, 1, 109, 1, 109, 1, 109, 1, 109, 1, 110, 1, 110, 1, 110, 1, 110, 1, 110, 1, 110, 1, 110, 1, 110, 1, 110, 1, 110, 1, 111, 1, 111, 1, 111, 1, 111, 1, 111, 1, 111, 1, 111, 1, 112, 1, 112, 1, 112, 1, 112, 1, 112, 1, 112, 1, 113, 1, 113, 1, 113, 1, 113, 1, 113, 1, 113, 1, 114, 1, 114, 1, 114, 1, 114, 1, 114, 1, 114, 1, 114, 1, 114, 1, 114, 1, 115, 1, 115, 1, 115, 1, 115, 1, 115, 1, 115, 1, 115, 1, 116, 1, 116, 1, 116, 1, 116, 1, 116, 1, 117, 1, 117, 1, 117, 1, 117, 1, 117, 1, 117, 1, 118, 1, 118, 1, 118, 1, 119, 1, 119, 1, 119, 1, 119, 1, 119, 1, 119, 1, 119, 1, 120, 1, 120, 1, 120, 1, 120, 1, 120, 1, 120, 1, 120, 1, 121, 1, 121, 1, 121, 1, 122, 1, 122, 1, 122, 1, 122, 1, 122, 1, 122, 1, 122, 1, 122, 1, 123, 1, 123, 1, 123, 1, 123, 1, 123, 1, 123, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 125, 1, 125, 1, 125, 1, 125, 1, 125, 1, 125, 1, 126, 1, 126, 1, 126, 1, 126, 1, 126, 1, 126, 1, 126, 1, 127, 1, 127, 1, 127, 1, 127, 1, 127, 1, 127, 1, 127, 1, 127, 1, 127, 1, 127, 1, 127, 1, 127, 1, 128, 1, 128, 1, 128, 1, 128, 1, 128, 1, 128, 1, 128, 1, 129, 1, 129, 1, 129, 1, 129, 1, 129, 1, 129, 1, 129, 1, 129, 1, 129, 1, 129, 1, 130, 1, 130, 1, 130, 1, 130, 1, 130, 1, 130, 1, 130, 1, 130, 1, 130, 1, 131, 1, 131, 1, 131, 1, 131, 1, 131, 1, 132, 1, 132, 1, 132, 1, 133, 1, 133, 1, 133, 1, 133, 1, 133, 1, 133, 1, 134, 1, 134, 1, 134, 1, 134, 1, 134, 1, 135, 1, 135, 1, 135, 1, 135, 1, 135, 1, 136, 1, 136, 1, 136, 1, 136, 1, 136, 1, 137, 1, 137, 1, 137, 1, 137, 1, 137, 1, 137, 1, 137, 1, 137, 1, 138, 1, 138, 1, 138, 1, 138, 1, 138, 1, 139, 1, 139, 1, 139, 1, 139, 1, 139, 1, 139, 1, 139, 1, 139, 1, 140, 1, 140, 1, 140, 1, 140, 1, 140, 1, 141, 1, 141, 1, 141, 1, 141, 1, 141, 1, 142, 1, 142, 1, 142, 1, 142, 1, 142, 1, 142, 1, 143, 1, 143, 1, 143, 1, 143, 1, 143, 1, 143, 1, 144, 1, 144, 1, 144, 1, 144, 1, 144, 1, 144, 1, 145, 1, 145, 1, 145, 1, 145, 1, 145, 1, 146, 1, 146, 1, 146, 1, 146, 1, 146, 1, 147, 1, 147, 1, 147, 1, 147, 1, 147, 1, 147, 1, 148, 1, 148, 1, 148, 1, 148, 1, 148, 1, 148, 1, 148, 1, 148, 1, 148, 1, 149, 1, 149, 1, 149, 1, 149, 1, 149, 1, 150, 1, 150, 1, 150, 1, 150, 1, 150, 1, 150, 1, 151, 1, 151, 1, 151, 1, 151, 1, 151, 1, 151, 1, 151, 1, 151, 1, 152, 1, 152, 1, 152, 1, 152, 1, 152, 1, 152, 1, 153, 1, 153, 1, 153, 1, 153, 1, 154, 1, 154, 1, 154, 1, 154, 1, 154, 1, 154, 1, 154, 1, 154, 1, 155, 1, 155, 1, 155, 1, 155, 1, 155, 1, 155, 1, 156, 1, 156, 1, 156, 1, 156, 1, 156, 1, 156, 1, 156, 1, 156, 1, 156, 1, 156, 1, 156, 1, 156, 1, 157, 1, 157, 1, 157, 1, 157, 1, 157, 1, 157, 1, 157, 1, 157, 1, 157, 1, 157, 1, 157, 1, 157, 1, 157, 1, 158, 1, 158, 1, 158, 1, 158, 1, 158, 1, 158, 1, 158, 1, 158, 1, 158, 1, 158, 1, 158, 1, 158, 1, 159, 1, 159, 1, 159, 1, 159, 1, 159, 1, 159, 1, 159, 1, 159, 1, 159, 1, 159, 1, 159, 1, 159, 1, 159, 1, 160, 1, 160, 1, 160, 1, 160, 1, 160, 1, 160, 1, 160, 1, 161, 1, 161, 1, 161, 1, 161, 1, 161, 1, 161, 1, 161, 1, 161, 1, 162, 1, 162, 1, 162, 1, 162, 1, 162, 1, 162, 1, 163, 1, 163, 1, 163, 1, 163, 1, 163, 1, 163, 1, 163, 1, 164, 1, 164, 1, 164, 1, 164, 1, 164, 1, 165, 1, 165, 1, 165, 1, 165, 1, 165, 1, 165, 1, 165, 1, 165, 1, 165, 1, 165, 1, 166, 1, 166, 1, 166, 1, 166, 1, 166, 1, 166, 1, 166, 1, 166, 1, 166, 1, 166, 1, 166, 1, 167, 1, 167, 1, 167, 1, 167, 1, 167, 1, 167, 1, 167, 1, 167, 1, 167, 1, 167, 1, 167, 1, 168, 1, 168, 1, 168, 1, 168, 1, 168, 1, 168, 1, 168, 1, 168, 1, 168, 1, 168, 1, 168, 1, 168, 1, 169, 1, 169, 1, 169, 1, 169, 1, 169, 1, 169, 1, 169, 1, 169, 1, 170, 1, 170, 1, 170, 1, 171, 1, 171, 1, 171, 1, 171, 3, 171, 1936, 8, 171, 1, 172, 1, 172, 1, 172, 1, 172, 1, 172, 1, 173, 1, 173, 1, 173, 1, 173, 1, 173, 1, 173, 1, 174, 1, 174, 1, 174, 1, 175, 1, 175, 1, 175, 1, 175, 1, 175, 1, 175, 1, 175, 1, 176, 1, 176, 1, 176, 1, 177, 1, 177, 1, 177, 1, 177, 1, 177, 1, 178, 1, 178, 1, 178, 1, 178, 1, 178, 1, 178, 1, 178, 1, 179, 1, 179, 1, 179, 1, 179, 1, 179, 1, 179, 1, 179, 1, 179, 1, 180, 1, 180, 1, 180, 1, 181, 1, 181, 1, 181, 1, 181, 1, 181, 1, 181, 1, 182, 1, 182, 1, 182, 1, 182, 1, 183, 1, 183, 1, 183, 1, 183, 1, 183, 1, 183, 1, 184, 1, 184, 1, 184, 1, 184, 1, 184, 1, 184, 1, 184, 1, 184, 1, 184, 1, 184, 1, 184, 1, 184, 1, 184, 1, 185, 1, 185, 1, 185, 1, 185, 1, 185, 1, 186, 1, 186, 1, 186, 1, 186, 1, 186, 1, 186, 1, 186, 1, 186, 1, 186, 1, 187, 1, 187, 1, 187, 1, 187, 1, 187, 1, 187, 1, 187, 1, 187, 1, 188, 1, 188, 1, 188, 1, 188, 1, 188, 1, 188, 1, 188, 1, 188, 1, 188, 1, 188, 1, 189, 1, 189, 1, 189, 1, 189, 1, 189, 1, 189, 1, 189, 1, 189, 1, 189, 1, 189, 1, 190, 1, 190, 1, 190, 1, 190, 1, 190, 1, 190, 1, 190, 1, 190, 1, 190, 1, 190, 1, 190, 1, 190, 1, 191, 1, 191, 1, 191, 1, 191, 1, 191, 1, 191, 1, 191, 1, 191, 1, 191, 1, 191, 1, 191, 1, 192, 1, 192, 1, 192, 1, 192, 1, 192, 1, 192, 1, 192, 1, 192, 1, 192, 1, 192, 1, 192, 1, 192, 1, 192, 1, 192, 1, 192, 1, 192, 1, 193, 1, 193, 1, 193, 1, 193, 1, 193, 1, 193, 1, 193, 1, 193, 1, 193, 1, 193, 1, 193, 1, 193, 1, 193, 1, 193, 1, 193, 1, 193, 1, 194, 1, 194, 1, 194, 1, 194, 1, 194, 1, 194, 1, 194, 1, 194, 1, 195, 1, 195, 1, 195, 1, 195, 1, 195, 1, 195, 1, 196, 1, 196, 1, 196, 1, 196, 1, 196, 1, 196, 1, 196, 1, 196, 1, 197, 1, 197, 1, 197, 1, 197, 1, 197, 1, 197, 1, 197, 1, 197, 1, 197, 1, 198, 1, 198, 1, 198, 1, 198, 1, 198, 1, 198, 1, 198, 1, 198, 1, 198, 1, 198, 1, 199, 1, 199, 1, 199, 1, 199, 1, 199, 1, 199, 1, 199, 1, 199, 1, 200, 1, 200, 1, 200, 1, 200, 1, 200, 1, 200, 1, 200, 1, 200, 1, 200, 1, 200, 1, 200, 1, 201, 1, 201, 1, 201, 1, 201, 1, 201, 1, 201, 1, 201, 1, 201, 1, 201, 1, 201, 1, 201, 1, 202, 1, 202, 1, 202, 1, 202, 1, 202, 1, 202, 1, 203, 1, 203, 1, 203, 1, 203, 1, 203, 1, 203, 1, 203, 1, 203, 1, 204, 1, 204, 1, 204, 1, 204, 1, 204, 1, 204, 1, 205, 1, 205, 1, 205, 1, 205, 1, 205, 1, 205, 1, 206, 1, 206, 1, 206, 1, 206, 1, 206, 1, 206, 1, 206, 1, 206, 1, 206, 1, 206, 1, 206, 1, 206, 1, 206, 1, 207, 1, 207, 1, 207, 1, 207, 1, 207, 1, 207, 1, 207, 1, 207, 1, 207, 1, 207, 1, 207, 1, 207, 1, 207, 1, 208, 1, 208, 1, 208, 1, 208, 1, 208, 1, 208, 1, 208, 1, 208, 1, 209, 1, 209, 1, 209, 1, 209, 1, 209, 1, 209, 1, 209, 1, 210, 1, 210, 1, 210, 1, 210, 1, 210, 1, 210, 1, 210, 1, 210, 1, 210, 1, 210, 1, 210, 1, 211, 1, 211, 1, 211, 1, 211, 1, 211, 1, 211, 1, 211, 1, 211, 1, 212, 1, 212, 1, 212, 1, 212, 1, 212, 1, 212, 1, 212, 1, 213, 1, 213, 1, 213, 1, 213, 1, 213, 1, 213, 1, 213, 1, 214, 1, 214, 1, 214, 1, 214, 1, 214, 1, 214, 1, 214, 1, 214, 1, 214, 1, 214, 1, 214, 1, 215, 1, 215, 1, 215, 1, 215, 1, 215, 1, 215, 1, 215, 1, 215, 1, 216, 1, 216, 1, 216, 1, 216, 1, 216, 1, 216, 1, 217, 1, 217, 1, 217, 1, 217, 1, 217, 1, 217, 1, 217, 1, 217, 1, 218, 1, 218, 1, 218, 1, 218, 1, 218, 1, 218, 1, 218, 1, 218, 1, 218, 1, 219, 1, 219, 1, 219, 1, 219, 1, 219, 1, 219, 1, 219, 1, 220, 1, 220, 1, 220, 1, 220, 1, 220, 1, 220, 1, 221, 1, 221, 1, 221, 1, 221, 1, 221, 1, 221, 1, 221, 1, 221, 1, 221, 1, 221, 1, 221, 3, 221, 2348, 8, 221, 1, 222, 1, 222, 1, 222, 1, 222, 1, 222, 1, 223, 1, 223, 1, 223, 1, 223, 1, 223, 1, 223, 1, 224, 1, 224, 1, 224, 1, 224, 1, 224, 1, 224, 1, 224, 1, 224, 1, 224, 1, 225, 1, 225, 1, 225, 1, 225, 1, 225, 1, 225, 1, 225, 1, 226, 1, 226, 1, 226, 1, 226, 1, 227, 1, 227, 1, 227, 1, 227, 1, 227, 1, 228, 1, 228, 1, 228, 1, 228, 1, 228, 1, 228, 1, 228, 1, 229, 1, 229, 1, 229, 1, 229, 1, 229, 1, 229, 1, 229, 1, 229, 1, 230, 1, 230, 1, 230, 1, 230, 1, 230, 1, 230, 1, 230, 1, 231, 1, 231, 1, 231, 1, 231, 1, 231, 1, 231, 1, 231, 1, 231, 1, 232, 1, 232, 1, 232, 1, 232, 1, 232, 1, 232, 1, 232, 1, 233, 1, 233, 1, 233, 1, 233, 1, 233, 1, 234, 1, 234, 1, 234, 1, 234, 1, 234, 1, 234, 1, 234, 1, 234, 1, 234, 1, 234, 1, 235, 1, 235, 1, 235, 1, 235, 1, 235, 1, 235, 1, 236, 1, 236, 1, 236, 1, 236, 1, 236, 1, 236, 1, 236, 1, 236, 1, 236, 1, 236, 1, 236, 1, 236, 1, 236, 1, 236, 1, 236, 1, 236, 1, 237, 1, 237, 1, 237, 1, 237, 1, 237, 1, 237, 1, 237, 1, 237, 1, 237, 1, 237, 1, 237, 1, 237, 1, 237, 1, 238, 1, 238, 1, 238, 1, 238, 1, 239, 1, 239, 1, 239, 1, 239, 1, 239, 1, 239, 1, 240, 1, 240, 1, 240, 1, 240, 1, 240, 1, 241, 1, 241, 1, 241, 1, 241, 1, 241, 1, 242, 1, 242, 1, 242, 1, 242, 1, 242, 1, 242, 1, 242, 1, 243, 1, 243, 1, 243, 1, 243, 1, 243, 1, 244, 1, 244, 1, 244, 1, 244, 1, 244, 1, 245, 1, 245, 1, 245, 1, 245, 1, 245, 1, 245, 1, 245, 1, 246, 1, 246, 1, 246, 1, 246, 1, 246, 1, 246, 1, 246, 1, 247, 1, 247, 1, 247, 1, 247, 1, 247, 1, 247, 1, 248, 1, 248, 1, 248, 1, 248, 1, 248, 1, 248, 1, 248, 1, 248, 1, 248, 1, 248, 1, 248, 1, 249, 1, 249, 1, 249, 1, 249, 1, 249, 1, 249, 1, 249, 1, 250, 1, 250, 1, 250, 1, 250, 1, 250, 1, 250, 1, 250, 1, 250, 1, 250, 1, 251, 1, 251, 1, 251, 1, 251, 1, 251, 1, 251, 1, 251, 1, 252, 1, 252, 1, 252, 1, 252, 1, 252, 1, 252, 1, 252, 1, 253, 1, 253, 1, 253, 1, 253, 1, 253, 1, 253, 1, 253, 1, 253, 1, 253, 1, 253, 1, 254, 1, 254, 1, 254, 1, 254, 1, 254, 1, 255, 1, 255, 1, 255, 1, 255, 1, 255, 1, 255, 1, 255, 1, 255, 1, 255, 1, 255, 1, 255, 1, 255, 1, 256, 1, 256, 1, 256, 1, 256, 1, 256, 1, 256, 1, 256, 1, 256, 1, 256, 1, 256, 1, 256, 1, 256, 1, 256, 1, 256, 1, 256, 1, 257, 1, 257, 1, 257, 1, 257, 1, 257, 1, 257, 1, 258, 1, 258, 1, 258, 1, 258, 1, 258, 1, 258, 1, 258, 1, 259, 1, 259, 1, 259, 1, 259, 1, 259, 1, 259, 1, 259, 1, 259, 1, 259, 1, 259, 1, 259, 1, 259, 1, 260, 1, 260, 1, 260, 1, 260, 1, 260, 1, 260, 1, 260, 1, 261, 1, 261, 1, 261, 1, 261, 1, 261, 1, 261, 1, 261, 1, 261, 1, 261, 1, 261, 1, 261, 1, 261, 1, 261, 1, 261, 1, 262, 1, 262, 1, 262, 1, 262, 1, 262, 1, 262, 1, 262, 1, 262, 1, 262, 1, 262, 1, 262, 1, 262, 1, 262, 3, 262, 2672, 8, 262, 1, 263, 1, 263, 1, 263, 1, 263, 1, 263, 1, 263, 1, 263, 1, 263, 1, 263, 1, 263, 1, 263, 1, 264, 1, 264, 1, 264, 1, 264, 1, 264, 1, 265, 1, 265, 1, 265, 1, 265, 1, 265, 1, 266, 1, 266, 1, 266, 1, 266, 1, 266, 1, 266, 1, 266, 1, 266, 1, 266, 1, 266, 1, 267, 1, 267, 1, 267, 1, 267, 1, 267, 1, 267, 1, 267, 1, 267, 1, 267, 1, 267, 1, 267, 1, 267, 1, 267, 1, 268, 1, 268, 1, 268, 1, 268, 1, 268, 1, 268, 1, 268, 1, 268, 1, 268, 1, 268, 1, 268, 1, 268, 1, 268, 1, 268, 1, 269, 1, 269, 1, 269, 1, 270, 1, 270, 1, 270, 1, 270, 1, 270, 1, 270, 1, 271, 1, 271, 1, 271, 1, 271, 1, 271, 1, 271, 1, 271, 1, 271, 1, 271, 1, 272, 1, 272, 1, 272, 1, 272, 1, 272, 1, 272, 1, 272, 1, 272, 1, 272, 1, 272, 1, 272, 1, 272, 1, 273, 1, 273, 1, 273, 1, 273, 1, 273, 1, 273, 1, 273, 1, 273, 1, 273, 1, 273, 1, 273, 1, 273, 1, 273, 1, 274, 1, 274, 1, 274, 1, 274, 1, 274, 1, 274, 1, 274, 1, 274, 1, 274, 1, 274, 1, 275, 1, 275, 1, 275, 1, 275, 1, 275, 1, 276, 1, 276, 1, 276, 1, 276, 1, 276, 1, 277, 1, 277, 1, 277, 1, 277, 1, 277, 1, 277, 1, 277, 1, 277, 1, 277, 1, 278, 1, 278, 1, 278, 1, 278, 1, 278, 1, 278, 1, 278, 1, 278, 1, 278, 1, 279, 1, 279, 1, 279, 1, 279, 1, 279, 1, 280, 1, 280, 1, 280, 1, 280, 1, 280, 1, 280, 1, 280, 1, 280, 1, 280, 1, 280, 1, 281, 1, 281, 1, 281, 1, 281, 1, 281, 1, 281, 1, 281, 1, 281, 1, 281, 1, 281, 1, 282, 1, 282, 1, 282, 1, 282, 1, 282, 1, 282, 1, 282, 1, 282, 1, 283, 1, 283, 1, 283, 1, 283, 1, 283, 1, 283, 1, 284, 1, 284, 1, 284, 1, 284, 1, 284, 1, 284, 1, 284, 1, 285, 1, 285, 1, 285, 1, 285, 1, 285, 1, 285, 1, 285, 1, 285, 1, 286, 1, 286, 1, 286, 1, 286, 1, 286, 1, 286, 1, 286, 1, 287, 1, 287, 1, 287, 1, 287, 1, 287, 1, 287, 1, 287, 1, 287, 1, 288, 1, 288, 1, 288, 1, 288, 1, 288, 1, 288, 1, 289, 1, 289, 1, 289, 1, 289, 1, 289, 1, 289, 1, 289, 1, 290, 1, 290, 1, 290, 1, 290, 1, 291, 1, 291, 1, 291, 1, 291, 1, 291, 1, 292, 1, 292, 1, 292, 1, 292, 1, 292, 1, 292, 1, 293, 1, 293, 1, 293, 1, 293, 1, 293, 1, 293, 1, 293, 1, 294, 1, 294, 1, 294, 1, 294, 1, 294, 1, 294, 1, 294, 1, 294, 1, 295, 1, 295, 1, 295, 1, 295, 1, 295, 1, 296, 1, 296, 1, 296, 1, 296, 1, 296, 1, 296, 1, 297, 1, 297, 1, 297, 1, 297, 1, 297, 1, 298, 1, 298, 1, 298, 1, 298, 1, 298, 1, 298, 1, 299, 1, 299, 1, 299, 1, 299, 1, 299, 1, 300, 1, 300, 1, 300, 1, 300, 1, 300, 1, 300, 1, 301, 1, 301, 1, 301, 1, 301, 1, 301, 1, 301, 1, 301, 1, 302, 1, 302, 1, 302, 1, 302, 1, 302, 1, 303, 1, 303, 1, 303, 1, 303, 1, 303, 1, 303, 1, 303, 1, 304, 1, 304, 1, 304, 1, 304, 1, 304, 1, 305, 1, 305, 1, 305, 1, 305, 1, 305, 1, 305, 1, 306, 1, 306, 1, 306, 1, 306, 1, 306, 1, 307, 1, 307, 1, 307, 3, 307, 2996, 8, 307, 1, 308, 1, 308, 1, 308, 1, 308, 1, 309, 1, 309, 1, 309, 1, 310, 1, 310, 1, 310, 1, 311, 1, 311, 1, 312, 1, 312, 1, 312, 1, 312, 3, 312, 3014, 8, 312, 1, 313, 1, 313, 1, 314, 1, 314, 1, 314, 1, 314, 3, 314, 3022, 8, 314, 1, 315, 1, 315, 1, 316, 1, 316, 1, 317, 1, 317, 1, 318, 1, 318, 1, 319, 1, 319, 1, 320, 1, 320, 1, 321, 1, 321, 1, 322, 1, 322, 1, 323, 1, 323, 1, 323, 1, 324, 1, 324, 1, 325, 1, 325, 1, 326, 1, 326, 1, 326, 1, 327, 1, 327, 1, 327, 1, 327, 1, 328, 1, 328, 1, 328, 1, 329, 1, 329, 1, 329, 1, 329, 5, 329, 3061, 8, 329, 10, 329, 12, 329, 3064, 9, 329, 1, 329, 1, 329, 1, 329, 1, 329, 1, 329, 5, 329, 3071, 8, 329, 10, 329, 12, 329, 3074, 9, 329, 1, 329, 1, 329, 1, 329, 1, 329, 1, 329, 5, 329, 3081, 8, 329, 10, 329, 12, 329, 3084, 9, 329, 1, 329, 3, 329, 3087, 8, 329, 1, 330, 1, 330, 1, 330, 1, 330, 5, 330, 3093, 8, 330, 10, 330, 12, 330, 3096, 9, 330, 1, 330, 1, 330, 1, 331, 4, 331, 3101, 8, 331, 11, 331, 12, 331, 3102, 1, 331, 1, 331, 1, 332, 4, 332, 3108, 8, 332, 11, 332, 12, 332, 3109, 1, 332, 1, 332, 1, 333, 4, 333, 3115, 8, 333, 11, 333, 12, 333, 3116, 1, 333, 1, 333, 1, 334, 4, 334, 3122, 8, 334, 11, 334, 12, 334, 3123, 1, 335, 4, 335, 3127, 8, 335, 11, 335, 12, 335, 3128, 1, 335, 1, 335, 1, 335, 1, 335, 1, 335, 3, 335, 3136, 8, 335, 1, 336, 1, 336, 1, 337, 4, 337, 3141, 8, 337, 11, 337, 12, 337, 3142, 1, 337, 3, 337, 3146, 8, 337, 1, 337, 1, 337, 1, 337, 1, 337, 3, 337, 3152, 8, 337, 1, 337, 1, 337, 3, 337, 3156, 8, 337, 1, 338, 4, 338, 3159, 8, 338, 11, 338, 12, 338, 3160, 1, 338, 3, 338, 3164, 8, 338, 1, 338, 1, 338, 1, 338, 1, 338, 3, 338, 3170, 8, 338, 1, 338, 1, 338, 3, 338, 3174, 8, 338, 1, 339, 4, 339, 3177, 8, 339, 11, 339, 12, 339, 3178, 1, 339, 3, 339, 3182, 8, 339, 1, 339, 1, 339, 1, 339, 1, 339, 1, 339, 3, 339, 3189, 8, 339, 1, 339, 1, 339, 1, 339, 3, 339, 3194, 8, 339, 1, 340, 1, 340, 1, 340, 4, 340, 3199, 8, 340, 11, 340, 12, 340, 3200, 1, 341, 1, 341, 1, 341, 1, 341, 5, 341, 3207, 8, 341, 10, 341, 12, 341, 3210, 9, 341, 1, 341, 1, 341, 1, 342, 4, 342, 3215, 8, 342, 11, 342, 12, 342, 3216, 1, 342, 1, 342, 5, 342, 3221, 8, 342, 10, 342, 12, 342, 3224, 9, 342, 1, 342, 1, 342, 4, 342, 3228, 8, 342, 11, 342, 12, 342, 3229, 3, 342, 3232, 8, 342, 1, 343, 1, 343, 3, 343, 3236, 8, 343, 1, 343, 4, 343, 3239, 8, 343, 11, 343, 12, 343, 3240, 1, 344, 1, 344, 1, 345, 1, 345, 1, 346, 1, 346, 1, 346, 1, 346, 1, 346, 1, 346, 5, 346, 3253, 8, 346, 10, 346, 12, 346, 3256, 9, 346, 1, 346, 3, 346, 3259, 8, 346, 1, 346, 3, 346, 3262, 8, 346, 1, 346, 1, 346, 1, 347, 1, 347, 1, 347, 1, 347, 1, 347, 5, 347, 3271, 8, 347, 10, 347, 12, 347, 3274, 9, 347, 1, 347, 1, 347, 1, 347, 1, 347, 3, 347, 3280, 8, 347, 1, 347, 1, 347, 1, 348, 4, 348, 3285, 8, 348, 11, 348, 12, 348, 3286, 1, 348, 1, 348, 1, 349, 1, 349, 1, 3272, 0, 350, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9, 19, 10, 21, 11, 23, 12, 25, 13, 27, 14, 29, 15, 31, 16, 33, 17, 35, 18, 37, 19, 39, 20, 41, 21, 43, 22, 45, 23, 47, 24, 49, 25, 51, 26, 53, 27, 55, 28, 57, 29, 59, 30, 61, 31, 63, 32, 65, 33, 67, 34, 69, 35, 71, 36, 73, 37, 75, 38, 77, 39, 79, 40, 81, 41, 83, 42, 85, 43, 87, 44, 89, 45, 91, 46, 93, 47, 95, 48, 97, 49, 99, 50, 101, 51, 103, 52, 105, 53, 107, 54, 109, 55, 111, 56, 113, 57, 115, 58, 117, 59, 119, 60, 121, 61, 123, 62, 125, 63, 127, 64, 129, 65, 131, 66, 133, 67, 135, 68, 137, 69, 139, 70, 141, 71, 143, 72, 145, 73, 147, 74, 149, 75, 151, 76, 153, 77, 155, 78, 157, 79, 159, 80, 161, 81, 163, 82, 165, 83, 167, 84, 169, 85, 171, 86, 173, 87, 175, 88, 177, 89, 179, 90, 181, 91, 183, 92, 185, 93, 187, 94, 189, 95, 191, 96, 193, 97, 195, 98, 197, 99, 199, 100, 201, 101, 203, 102, 205, 103, 207, 104, 209, 105, 211, 106, 213, 107, 215, 108, 217, 109, 219, 110, 221, 111, 223, 112, 225, 113, 227, 114, 229, 115, 231, 116, 233, 117, 235, 118, 237, 119, 239, 120, 241, 121, 243, 122, 245, 123, 247, 124, 249, 125, 251, 126, 253, 127, 255, 128, 257, 129, 259, 130, 261, 131, 263, 132, 265, 133, 267, 134, 269, 135, 271, 136, 273, 137, 275, 138, 277, 139, 279, 140, 281, 141, 283, 142, 285, 143, 287, 144, 289, 145, 291, 146, 293, 147, 295, 148, 297, 149, 299, 150, 301, 151, 303, 152, 305, 153, 307, 154, 309, 155, 311, 156, 313, 157, 315, 158, 317, 159, 319, 160, 321, 161, 323, 162, 325, 163, 327, 164, 329, 165, 331, 166, 333, 167, 335, 168, 337, 169, 339, 170, 341, 171, 343, 172, 345, 173, 347, 174, 349, 175, 351, 176, 353, 177, 355, 178, 357, 179, 359, 180, 361, 181, 363, 182, 365, 183, 367, 184, 369, 185, 371, 186, 373, 187, 375, 188, 377, 189, 379, 190, 381, 191, 383, 192, 385, 193, 387, 194, 389, 195, 391, 196, 393, 197, 395, 198, 397, 199, 399, 200, 401, 201, 403, 202, 405, 203, 407, 204, 409, 205, 411, 206, 413, 207, 415, 208, 417, 209, 419, 210, 421, 211, 423, 212, 425, 213, 427, 214, 429, 215, 431, 216, 433, 217, 435, 218, 437, 219, 439, 220, 441, 221, 443, 222, 445, 223, 447, 224, 449, 225, 451, 226, 453, 227, 455, 228, 457, 229, 459, 230, 461, 231, 463, 232, 465, 233, 467, 234, 469, 235, 471, 236, 473, 237, 475, 238, 477, 239, 479, 240, 481, 241, 483, 242, 485, 243, 487, 244, 489, 245, 491, 246, 493, 247, 495, 248, 497, 249, 499, 250, 501, 251, 503, 252, 505, 253, 507, 254, 509, 255, 511, 256, 513, 257, 515, 258, 517, 259, 519, 260, 521, 261, 523, 262, 525, 263, 527, 264, 529, 265, 531, 266, 533, 267, 535, 268, 537, 269, 539, 270, 541, 271, 543, 272, 545, 273, 547, 274, 549, 275, 551, 276, 553, 277, 555, 278, 557, 279, 559, 280, 561, 281, 563, 282, 565, 283, 567, 284, 569, 285, 571, 286, 573, 287, 575, 288, 577, 289, 579, 290, 581, 291, 583, 292, 585, 293, 587, 294, 589, 295, 591, 296, 593, 297, 595, 298, 597, 299, 599, 300, 601, 301, 603, 302, 605, 303, 607, 304, 609, 305, 611, 306, 613, 307, 615, 308, 617, 309, 619, 310, 621, 311, 623, 312, 625, 313, 627, 314, 629, 315, 631, 316, 633, 317, 635, 318, 637, 319, 639, 320, 641, 321, 643, 322, 645, 323, 647, 324, 649, 325, 651, 326, 653, 327, 655, 328, 657, 329, 659, 330, 661, 331, 663, 332, 665, 333, 667, 334, 669, 335, 671, 336, 673, 337, 675, 338, 677, 339, 679, 340, 681, 341, 683, 342, 685, 0, 687, 0, 689, 0, 691, 0, 693, 343, 695, 344, 697, 345, 699, 346, 1, 0, 10, 2, 0, 39, 39, 92, 92, 1, 0, 39, 39, 1, 0, 34, 34, 2, 0, 34, 34, 92, 92, 1, 0, 96, 96, 2, 0, 43, 43, 45, 45, 1, 0, 48, 57, 1, 0, 65, 90, 2, 0, 10, 10, 13, 13, 3, 0, 9, 10, 13, 13, 32, 32, 3338, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 0, 23, 1, 0, 0, 0, 0, 25, 1, 0, 0, 0, 0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 0, 0, 0, 0, 35, 1, 0, 0, 0, 0, 37, 1, 0, 0, 0, 0, 39, 1, 0, 0, 0, 0, 41, 1, 0, 0, 0, 0, 43, 1, 0, 0, 0, 0, 45, 1, 0, 0, 0, 0, 47, 1, 0, 0, 0, 0, 49, 1, 0, 0, 0, 0, 51, 1, 0, 0, 0, 0, 53, 1, 0, 0, 0, 0, 55, 1, 0, 0, 0, 0, 57, 1, 0, 0, 0, 0, 59, 1, 0, 0, 0, 0, 61, 1, 0, 0, 0, 0, 63, 1, 0, 0, 0, 0, 65, 1, 0, 0, 0, 0, 67, 1, 0, 0, 0, 0, 69, 1, 0, 0, 0, 0, 71, 1, 0, 0, 0, 0, 73, 1, 0, 0, 0, 0, 75, 1, 0, 0, 0, 0, 77, 1, 0, 0, 0, 0, 79, 1, 0, 0, 0, 0, 81, 1, 0, 0, 0, 0, 83, 1, 0, 0, 0, 0, 85, 1, 0, 0, 0, 0, 87, 1, 0, 0, 0, 0, 89, 1, 0, 0, 0, 0, 91, 1, 0, 0, 0, 0, 93, 1, 0, 0, 0, 0, 95, 1, 0, 0, 0, 0, 97, 1, 0, 0, 0, 0, 99, 1, 0, 0, 0, 0, 101, 1, 0, 0, 0, 0, 103, 1, 0, 0, 0, 0, 105, 1, 0, 0, 0, 0, 107, 1, 0, 0, 0, 0, 109, 1, 0, 0, 0, 0, 111, 1, 0, 0, 0, 0, 113, 1, 0, 0, 0, 0, 115, 1, 0, 0, 0, 0, 117, 1, 0, 0, 0, 0, 119, 1, 0, 0, 0, 0, 121, 1, 0, 0, 0, 0, 123, 1, 0, 0, 0, 0, 125, 1, 0, 0, 0, 0, 127, 1, 0, 0, 0, 0, 129, 1, 0, 0, 0, 0, 131, 1, 0, 0, 0, 0, 133, 1, 0, 0, 0, 0, 135, 1, 0, 0, 0, 0, 137, 1, 0, 0, 0, 0, 139, 1, 0, 0, 0, 0, 141, 1, 0, 0, 0, 0, 143, 1, 0, 0, 0, 0, 145, 1, 0, 0, 0, 0, 147, 1, 0, 0, 0, 0, 149, 1, 0, 0, 0, 0, 151, 1, 0, 0, 0, 0, 153, 1, 0, 0, 0, 0, 155, 1, 0, 0, 0, 0, 157, 1, 0, 0, 0, 0, 159, 1, 0, 0, 0, 0, 161, 1, 0, 0, 0, 0, 163, 1, 0, 0, 0, 0, 165, 1, 0, 0, 0, 0, 167, 1, 0, 0, 0, 0, 169, 1, 0, 0, 0, 0, 171, 1, 0, 0, 0, 0, 173, 1, 0, 0, 0, 0, 175, 1, 0, 0, 0, 0, 177, 1, 0, 0, 0, 0, 179, 1, 0, 0, 0, 0, 181, 1, 0, 0, 0, 0, 183, 1, 0, 0, 0, 0, 185, 1, 0, 0, 0, 0, 187, 1, 0, 0, 0, 0, 189, 1, 0, 0, 0, 0, 191, 1, 0, 0, 0, 0, 193, 1, 0, 0, 0, 0, 195, 1, 0, 0, 0, 0, 197, 1, 0, 0, 0, 0, 199, 1, 0, 0, 0, 0, 201, 1, 0, 0, 0, 0, 203, 1, 0, 0, 0, 0, 205, 1, 0, 0, 0, 0, 207, 1, 0, 0, 0, 0, 209, 1, 0, 0, 0, 0, 211, 1, 0, 0, 0, 0, 213, 1, 0, 0, 0, 0, 215, 1, 0, 0, 0, 0, 217, 1, 0, 0, 0, 0, 219, 1, 0, 0, 0, 0, 221, 1, 0, 0, 0, 0, 223, 1, 0, 0, 0, 0, 225, 1, 0, 0, 0, 0, 227, 1, 0, 0, 0, 0, 229, 1, 0, 0, 0, 0, 231, 1, 0, 0, 0, 0, 233, 1, 0, 0, 0, 0, 235, 1, 0, 0, 0, 0, 237, 1, 0, 0, 0, 0, 239, 1, 0, 0, 0, 0, 241, 1, 0, 0, 0, 0, 243, 1, 0, 0, 0, 0, 245, 1, 0, 0, 0, 0, 247, 1, 0, 0, 0, 0, 249, 1, 0, 0, 0, 0, 251, 1, 0, 0, 0, 0, 253, 1, 0, 0, 0, 0, 255, 1, 0, 0, 0, 0, 257, 1, 0, 0, 0, 0, 259, 1, 0, 0, 0, 0, 261, 1, 0, 0, 0, 0, 263, 1, 0, 0, 0, 0, 265, 1, 0, 0, 0, 0, 267, 1, 0, 0, 0, 0, 269, 1, 0, 0, 0, 0, 271, 1, 0, 0, 0, 0, 273, 1, 0, 0, 0, 0, 275, 1, 0, 0, 0, 0, 277, 1, 0, 0, 0, 0, 279, 1, 0, 0, 0, 0, 281, 1, 0, 0, 0, 0, 283, 1, 0, 0, 0, 0, 285, 1, 0, 0, 0, 0, 287, 1, 0, 0, 0, 0, 289, 1, 0, 0, 0, 0, 291, 1, 0, 0, 0, 0, 293, 1, 0, 0, 0, 0, 295, 1, 0, 0, 0, 0, 297, 1, 0, 0, 0, 0, 299, 1, 0, 0, 0, 0, 301, 1, 0, 0, 0, 0, 303, 1, 0, 0, 0, 0, 305, 1, 0, 0, 0, 0, 307, 1, 0, 0, 0, 0, 309, 1, 0, 0, 0, 0, 311, 1, 0, 0, 0, 0, 313, 1, 0, 0, 0, 0, 315, 1, 0, 0, 0, 0, 317, 1, 0, 0, 0, 0, 319, 1, 0, 0, 0, 0, 321, 1, 0, 0, 0, 0, 323, 1, 0, 0, 0, 0, 325, 1, 0, 0, 0, 0, 327, 1, 0, 0, 0, 0, 329, 1, 0, 0, 0, 0, 331, 1, 0, 0, 0, 0, 333, 1, 0, 0, 0, 0, 335, 1, 0, 0, 0, 0, 337, 1, 0, 0, 0, 0, 339, 1, 0, 0, 0, 0, 341, 1, 0, 0, 0, 0, 343, 1, 0, 0, 0, 0, 345, 1, 0, 0, 0, 0, 347, 1, 0, 0, 0, 0, 349, 1, 0, 0, 0, 0, 351, 1, 0, 0, 0, 0, 353, 1, 0, 0, 0, 0, 355, 1, 0, 0, 0, 0, 357, 1, 0, 0, 0, 0, 359, 1, 0, 0, 0, 0, 361, 1, 0, 0, 0, 0, 363, 1, 0, 0, 0, 0, 365, 1, 0, 0, 0, 0, 367, 1, 0, 0, 0, 0, 369, 1, 0, 0, 0, 0, 371, 1, 0, 0, 0, 0, 373, 1, 0, 0, 0, 0, 375, 1, 0, 0, 0, 0, 377, 1, 0, 0, 0, 0, 379, 1, 0, 0, 0, 0, 381, 1, 0, 0, 0, 0, 383, 1, 0, 0, 0, 0, 385, 1, 0, 0, 0, 0, 387, 1, 0, 0, 0, 0, 389, 1, 0, 0, 0, 0, 391, 1, 0, 0, 0, 0, 393, 1, 0, 0, 0, 0, 395, 1, 0, 0, 0, 0, 397, 1, 0, 0, 0, 0, 399, 1, 0, 0, 0, 0, 401, 1, 0, 0, 0, 0, 403, 1, 0, 0, 0, 0, 405, 1, 0, 0, 0, 0, 407, 1, 0, 0, 0, 0, 409, 1, 0, 0, 0, 0, 411, 1, 0, 0, 0, 0, 413, 1, 0, 0, 0, 0, 415, 1, 0, 0, 0, 0, 417, 1, 0, 0, 0, 0, 419, 1, 0, 0, 0, 0, 421, 1, 0, 0, 0, 0, 423, 1, 0, 0, 0, 0, 425, 1, 0, 0, 0, 0, 427, 1, 0, 0, 0, 0, 429, 1, 0, 0, 0, 0, 431, 1, 0, 0, 0, 0, 433, 1, 0, 0, 0, 0, 435, 1, 0, 0, 0, 0, 437, 1, 0, 0, 0, 0, 439, 1, 0, 0, 0, 0, 441, 1, 0, 0, 0, 0, 443, 1, 0, 0, 0, 0, 445, 1, 0, 0, 0, 0, 447, 1, 0, 0, 0, 0, 449, 1, 0, 0, 0, 0, 451, 1, 0, 0, 0, 0, 453, 1, 0, 0, 0, 0, 455, 1, 0, 0, 0, 0, 457, 1, 0, 0, 0, 0, 459, 1, 0, 0, 0, 0, 461, 1, 0, 0, 0, 0, 463, 1, 0, 0, 0, 0, 465, 1, 0, 0, 0, 0, 467, 1, 0, 0, 0, 0, 469, 1, 0, 0, 0, 0, 471, 1, 0, 0, 0, 0, 473, 1, 0, 0, 0, 0, 475, 1, 0, 0, 0, 0, 477, 1, 0, 0, 0, 0, 479, 1, 0, 0, 0, 0, 481, 1, 0, 0, 0, 0, 483, 1, 0, 0, 0, 0, 485, 1, 0, 0, 0, 0, 487, 1, 0, 0, 0, 0, 489, 1, 0, 0, 0, 0, 491, 1, 0, 0, 0, 0, 493, 1, 0, 0, 0, 0, 495, 1, 0, 0, 0, 0, 497, 1, 0, 0, 0, 0, 499, 1, 0, 0, 0, 0, 501, 1, 0, 0, 0, 0, 503, 1, 0, 0, 0, 0, 505, 1, 0, 0, 0, 0, 507, 1, 0, 0, 0, 0, 509, 1, 0, 0, 0, 0, 511, 1, 0, 0, 0, 0, 513, 1, 0, 0, 0, 0, 515, 1, 0, 0, 0, 0, 517, 1, 0, 0, 0, 0, 519, 1, 0, 0, 0, 0, 521, 1, 0, 0, 0, 0, 523, 1, 0, 0, 0, 0, 525, 1, 0, 0, 0, 0, 527, 1, 0, 0, 0, 0, 529, 1, 0, 0, 0, 0, 531, 1, 0, 0, 0, 0, 533, 1, 0, 0, 0, 0, 535, 1, 0, 0, 0, 0, 537, 1, 0, 0, 0, 0, 539, 1, 0, 0, 0, 0, 541, 1, 0, 0, 0, 0, 543, 1, 0, 0, 0, 0, 545, 1, 0, 0, 0, 0, 547, 1, 0, 0, 0, 0, 549, 1, 0, 0, 0, 0, 551, 1, 0, 0, 0, 0, 553, 1, 0, 0, 0, 0, 555, 1, 0, 0, 0, 0, 557, 1, 0, 0, 0, 0, 559, 1, 0, 0, 0, 0, 561, 1, 0, 0, 0, 0, 563, 1, 0, 0, 0, 0, 565, 1, 0, 0, 0, 0, 567, 1, 0, 0, 0, 0, 569, 1, 0, 0, 0, 0, 571, 1, 0, 0, 0, 0, 573, 1, 0, 0, 0, 0, 575, 1, 0, 0, 0, 0, 577, 1, 0, 0, 0, 0, 579, 1, 0, 0, 0, 0, 581, 1, 0, 0, 0, 0, 583, 1, 0, 0, 0, 0, 585, 1, 0, 0, 0, 0, 587, 1, 0, 0, 0, 0, 589, 1, 0, 0, 0, 0, 591, 1, 0, 0, 0, 0, 593, 1, 0, 0, 0, 0, 595, 1, 0, 0, 0, 0, 597, 1, 0, 0, 0, 0, 599, 1, 0, 0, 0, 0, 601, 1, 0, 0, 0, 0, 603, 1, 0, 0, 0, 0, 605, 1, 0, 0, 0, 0, 607, 1, 0, 0, 0, 0, 609, 1, 0, 0, 0, 0, 611, 1, 0, 0, 0, 0, 613, 1, 0, 0, 0, 0, 615, 1, 0, 0, 0, 0, 617, 1, 0, 0, 0, 0, 619, 1, 0, 0, 0, 0, 621, 1, 0, 0, 0, 0, 623, 1, 0, 0, 0, 0, 625, 1, 0, 0, 0, 0, 627, 1, 0, 0, 0, 0, 629, 1, 0, 0, 0, 0, 631, 1, 0, 0, 0, 0, 633, 1, 0, 0, 0, 0, 635, 1, 0, 0, 0, 0, 637, 1, 0, 0, 0, 0, 639, 1, 0, 0, 0, 0, 641, 1, 0, 0, 0, 0, 643, 1, 0, 0, 0, 0, 645, 1, 0, 0, 0, 0, 647, 1, 0, 0, 0, 0, 649, 1, 0, 0, 0, 0, 651, 1, 0, 0, 0, 0, 653, 1, 0, 0, 0, 0, 655, 1, 0, 0, 0, 0, 657, 1, 0, 0, 0, 0, 659, 1, 0, 0, 0, 0, 661, 1, 0, 0, 0, 0, 663, 1, 0, 0, 0, 0, 665, 1, 0, 0, 0, 0, 667, 1, 0, 0, 0, 0, 669, 1, 0, 0, 0, 0, 671, 1, 0, 0, 0, 0, 673, 1, 0, 0, 0, 0, 675, 1, 0, 0, 0, 0, 677, 1, 0, 0, 0, 0, 679, 1, 0, 0, 0, 0, 681, 1, 0, 0, 0, 0, 683, 1, 0, 0, 0, 0, 693, 1, 0, 0, 0, 0, 695, 1, 0, 0, 0, 0, 697, 1, 0, 0, 0, 0, 699, 1, 0, 0, 0, 1, 701, 1, 0, 0, 0, 3, 703, 1, 0, 0, 0, 5, 705, 1, 0, 0, 0, 7, 707, 1, 0, 0, 0, 9, 709, 1, 0, 0, 0, 11, 711, 1, 0, 0, 0, 13, 713, 1, 0, 0, 0, 15, 715, 1, 0, 0, 0, 17, 719, 1, 0, 0, 0, 19, 725, 1, 0, 0, 0, 21, 729, 1, 0, 0, 0, 23, 735, 1, 0, 0, 0, 25, 742, 1, 0, 0, 0, 27, 750, 1, 0, 0, 0, 29, 754, 1, 0, 0, 0, 31, 759, 1, 0, 0, 0, 33, 763, 1, 0, 0, 0, 35, 773, 1, 0, 0, 0, 37, 781, 1, 0, 0, 0, 39, 787, 1, 0, 0, 0, 41, 790, 1, 0, 0, 0, 43, 794, 1, 0, 0, 0, 45, 797, 1, 0, 0, 0, 47, 811, 1, 0, 0, 0, 49, 819, 1, 0, 0, 0, 51, 824, 1, 0, 0, 0, 53, 831, 1, 0, 0, 0, 55, 839, 1, 0, 0, 0, 57, 842, 1, 0, 0, 0, 59, 848, 1, 0, 0, 0, 61, 856, 1, 0, 0, 0, 63, 861, 1, 0, 0, 0, 65, 866, 1, 0, 0, 0, 67, 874, 1, 0, 0, 0, 69, 883, 1, 0, 0, 0, 71, 890, 1, 0, 0, 0, 73, 896, 1, 0, 0, 0, 75, 902, 1, 0, 0, 0, 77, 910, 1, 0, 0, 0, 79, 920, 1, 0, 0, 0, 81, 928, 1, 0, 0, 0, 83, 936, 1, 0, 0, 0, 85, 947, 1, 0, 0, 0, 87, 954, 1, 0, 0, 0, 89, 962, 1, 0, 0, 0, 91, 970, 1, 0, 0, 0, 93, 977, 1, 0, 0, 0, 95, 985, 1, 0, 0, 0, 97, 997, 1, 0, 0, 0, 99, 1005, 1, 0, 0, 0, 101, 1017, 1, 0, 0, 0, 103, 1028, 1, 0, 0, 0, 105, 1033, 1, 0, 0, 0, 107, 1040, 1, 0, 0, 0, 109, 1046, 1, 0, 0, 0, 111, 1051, 1, 0, 0, 0, 113, 1059, 1, 0, 0, 0, 115, 1072, 1, 0, 0, 0, 117, 1085, 1, 0, 0, 0, 119, 1103, 1, 0, 0, 0, 121, 1116, 1, 0, 0, 0, 123, 1120, 1, 0, 0, 0, 125, 1125, 1, 0, 0, 0, 127, 1135, 1, 0, 0, 0, 129, 1140, 1, 0, 0, 0, 131, 1149, 1, 0, 0, 0, 133, 1159, 1, 0, 0, 0, 135, 1167, 1, 0, 0, 0, 137, 1176, 1, 0, 0, 0, 139, 1189, 1, 0, 0, 0, 141, 1197, 1, 0, 0, 0, 143, 1205, 1, 0, 0, 0, 145, 1212, 1, 0, 0, 0, 147, 1222, 1, 0, 0, 0, 149, 1227, 1, 0, 0, 0, 151, 1236, 1, 0, 0, 0, 153, 1240, 1, 0, 0, 0, 155, 1252, 1, 0, 0, 0, 157, 1262, 1, 0, 0, 0, 159, 1271, 1, 0, 0, 0, 161, 1282, 1, 0, 0, 0, 163, 1286, 1, 0, 0, 0, 165, 1291, 1, 0, 0, 0, 167, 1296, 1, 0, 0, 0, 169, 1300, 1, 0, 0, 0, 171, 1307, 1, 0, 0, 0, 173, 1315, 1, 0, 0, 0, 175, 1322, 1, 0, 0, 0, 177, 1331, 1, 0, 0, 0, 179, 1339, 1, 0, 0, 0, 181, 1346, 1, 0, 0, 0, 183, 1354, 1, 0, 0, 0, 185, 1361, 1, 0, 0, 0, 187, 1370, 1, 0, 0, 0, 189, 1379, 1, 0, 0, 0, 191, 1387, 1, 0, 0, 0, 193, 1393, 1, 0, 0, 0, 195, 1399, 1, 0, 0, 0, 197, 1406, 1, 0, 0, 0, 199, 1413, 1, 0, 0, 0, 201, 1424, 1, 0, 0, 0, 203, 1430, 1, 0, 0, 0, 205, 1440, 1, 0, 0, 0, 207, 1444, 1, 0, 0, 0, 209, 1452, 1, 0, 0, 0, 211, 1459, 1, 0, 0, 0, 213, 1469, 1, 0, 0, 0, 215, 1474, 1, 0, 0, 0, 217, 1479, 1, 0, 0, 0, 219, 1488, 1, 0, 0, 0, 221, 1498, 1, 0, 0, 0, 223, 1508, 1, 0, 0, 0, 225, 1515, 1, 0, 0, 0, 227, 1521, 1, 0, 0, 0, 229, 1527, 1, 0, 0, 0, 231, 1536, 1, 0, 0, 0, 233, 1543, 1, 0, 0, 0, 235, 1548, 1, 0, 0, 0, 237, 1554, 1, 0, 0, 0, 239, 1557, 1, 0, 0, 0, 241, 1564, 1, 0, 0, 0, 243, 1571, 1, 0, 0, 0, 245, 1574, 1, 0, 0, 0, 247, 1582, 1, 0, 0, 0, 249, 1588, 1, 0, 0, 0, 251, 1596, 1, 0, 0, 0, 253, 1602, 1, 0, 0, 0, 255, 1609, 1, 0, 0, 0, 257, 1621, 1, 0, 0, 0, 259, 1628, 1, 0, 0, 0, 261, 1638, 1, 0, 0, 0, 263, 1647, 1, 0, 0, 0, 265, 1652, 1, 0, 0, 0, 267, 1655, 1, 0, 0, 0, 269, 1661, 1, 0, 0, 0, 271, 1666, 1, 0, 0, 0, 273, 1671, 1, 0, 0, 0, 275, 1676, 1, 0, 0, 0, 277, 1684, 1, 0, 0, 0, 279, 1689, 1, 0, 0, 0, 281, 1697, 1, 0, 0, 0, 283, 1702, 1, 0, 0, 0, 285, 1707, 1, 0, 0, 0, 287, 1713, 1, 0, 0, 0, 289, 1719, 1, 0, 0, 0, 291, 1725, 1, 0, 0, 0, 293, 1730, 1, 0, 0, 0, 295, 1735, 1, 0, 0, 0, 297, 1741, 1, 0, 0, 0, 299, 1750, 1, 0, 0, 0, 301, 1755, 1, 0, 0, 0, 303, 1761, 1, 0, 0, 0, 305, 1769, 1, 0, 0, 0, 307, 1775, 1, 0, 0, 0, 309, 1779, 1, 0, 0, 0, 311, 1787, 1, 0, 0, 0, 313, 1793, 1, 0, 0, 0, 315, 1805, 1, 0, 0, 0, 317, 1818, 1, 0, 0, 0, 319, 1830, 1, 0, 0, 0, 321, 1843, 1, 0, 0, 0, 323, 1850, 1, 0, 0, 0, 325, 1858, 1, 0, 0, 0, 327, 1864, 1, 0, 0, 0, 329, 1871, 1, 0, 0, 0, 331, 1876, 1, 0, 0, 0, 333, 1886, 1, 0, 0, 0, 335, 1897, 1, 0, 0, 0, 337, 1908, 1, 0, 0, 0, 339, 1920, 1, 0, 0, 0, 341, 1928, 1, 0, 0, 0, 343, 1935, 1, 0, 0, 0, 345, 1937, 1, 0, 0, 0, 347, 1942, 1, 0, 0, 0, 349, 1948, 1, 0, 0, 0, 351, 1951, 1, 0, 0, 0, 353, 1958, 1, 0, 0, 0, 355, 1961, 1, 0, 0, 0, 357, 1966, 1, 0, 0, 0, 359, 1973, 1, 0, 0, 0, 361, 1981, 1, 0, 0, 0, 363, 1984, 1, 0, 0, 0, 365, 1990, 1, 0, 0, 0, 367, 1994, 1, 0, 0, 0, 369, 2000, 1, 0, 0, 0, 371, 2013, 1, 0, 0, 0, 373, 2018, 1, 0, 0, 0, 375, 2027, 1, 0, 0, 0, 377, 2035, 1, 0, 0, 0, 379, 2045, 1, 0, 0, 0, 381, 2055, 1, 0, 0, 0, 383, 2067, 1, 0, 0, 0, 385, 2078, 1, 0, 0, 0, 387, 2094, 1, 0, 0, 0, 389, 2110, 1, 0, 0, 0, 391, 2118, 1, 0, 0, 0, 393, 2124, 1, 0, 0, 0, 395, 2132, 1, 0, 0, 0, 397, 2141, 1, 0, 0, 0, 399, 2151, 1, 0, 0, 0, 401, 2159, 1, 0, 0, 0, 403, 2170, 1, 0, 0, 0, 405, 2181, 1, 0, 0, 0, 407, 2187, 1, 0, 0, 0, 409, 2195, 1, 0, 0, 0, 411, 2201, 1, 0, 0, 0, 413, 2207, 1, 0, 0, 0, 415, 2220, 1, 0, 0, 0, 417, 2233, 1, 0, 0, 0, 419, 2241, 1, 0, 0, 0, 421, 2248, 1, 0, 0, 0, 423, 2259, 1, 0, 0, 0, 425, 2267, 1, 0, 0, 0, 427, 2274, 1, 0, 0, 0, 429, 2281, 1, 0, 0, 0, 431, 2292, 1, 0, 0, 0, 433, 2300, 1, 0, 0, 0, 435, 2306, 1, 0, 0, 0, 437, 2314, 1, 0, 0, 0, 439, 2323, 1, 0, 0, 0, 441, 2330, 1, 0, 0, 0, 443, 2347, 1, 0, 0, 0, 445, 2349, 1, 0, 0, 0, 447, 2354, 1, 0, 0, 0, 449, 2360, 1, 0, 0, 0, 451, 2369, 1, 0, 0, 0, 453, 2376, 1, 0, 0, 0, 455, 2380, 1, 0, 0, 0, 457, 2385, 1, 0, 0, 0, 459, 2392, 1, 0, 0, 0, 461, 2400, 1, 0, 0, 0, 463, 2407, 1, 0, 0, 0, 465, 2415, 1, 0, 0, 0, 467, 2422, 1, 0, 0, 0, 469, 2427, 1, 0, 0, 0, 471, 2437, 1, 0, 0, 0, 473, 2443, 1, 0, 0, 0, 475, 2459, 1, 0, 0, 0, 477, 2472, 1, 0, 0, 0, 479, 2476, 1, 0, 0, 0, 481, 2482, 1, 0, 0, 0, 483, 2487, 1, 0, 0, 0, 485, 2492, 1, 0, 0, 0, 487, 2499, 1, 0, 0, 0, 489, 2504, 1, 0, 0, 0, 491, 2509, 1, 0, 0, 0, 493, 2516, 1, 0, 0, 0, 495, 2523, 1, 0, 0, 0, 497, 2529, 1, 0, 0, 0, 499, 2540, 1, 0, 0, 0, 501, 2547, 1, 0, 0, 0, 503, 2556, 1, 0, 0, 0, 505, 2563, 1, 0, 0, 0, 507, 2570, 1, 0, 0, 0, 509, 2580, 1, 0, 0, 0, 511, 2585, 1, 0, 0, 0, 513, 2597, 1, 0, 0, 0, 515, 2612, 1, 0, 0, 0, 517, 2618, 1, 0, 0, 0, 519, 2625, 1, 0, 0, 0, 521, 2637, 1, 0, 0, 0, 523, 2644, 1, 0, 0, 0, 525, 2671, 1, 0, 0, 0, 527, 2673, 1, 0, 0, 0, 529, 2684, 1, 0, 0, 0, 531, 2689, 1, 0, 0, 0, 533, 2694, 1, 0, 0, 0, 535, 2704, 1, 0, 0, 0, 537, 2717, 1, 0, 0, 0, 539, 2731, 1, 0, 0, 0, 541, 2734, 1, 0, 0, 0, 543, 2740, 1, 0, 0, 0, 545, 2749, 1, 0, 0, 0, 547, 2761, 1, 0, 0, 0, 549, 2774, 1, 0, 0, 0, 551, 2784, 1, 0, 0, 0, 553, 2789, 1, 0, 0, 0, 555, 2794, 1, 0, 0, 0, 557, 2803, 1, 0, 0, 0, 559, 2812, 1, 0, 0, 0, 561, 2817, 1, 0, 0, 0, 563, 2827, 1, 0, 0, 0, 565, 2837, 1, 0, 0, 0, 567, 2845, 1, 0, 0, 0, 569, 2851, 1, 0, 0, 0, 571, 2858, 1, 0, 0, 0, 573, 2866, 1, 0, 0, 0, 575, 2873, 1, 0, 0, 0, 577, 2881, 1, 0, 0, 0, 579, 2887, 1, 0, 0, 0, 581, 2894, 1, 0, 0, 0, 583, 2898, 1, 0, 0, 0, 585, 2903, 1, 0, 0, 0, 587, 2909, 1, 0, 0, 0, 589, 2916, 1, 0, 0, 0, 591, 2924, 1, 0, 0, 0, 593, 2929, 1, 0, 0, 0, 595, 2935, 1, 0, 0, 0, 597, 2940, 1, 0, 0, 0, 599, 2946, 1, 0, 0, 0, 601, 2951, 1, 0, 0, 0, 603, 2957, 1, 0, 0, 0, 605, 2964, 1, 0, 0, 0, 607, 2969, 1, 0, 0, 0, 609, 2976, 1, 0, 0, 0, 611, 2981, 1, 0, 0, 0, 613, 2987, 1, 0, 0, 0, 615, 2995, 1, 0, 0, 0, 617, 2997, 1, 0, 0, 0, 619, 3001, 1, 0, 0, 0, 621, 3004, 1, 0, 0, 0, 623, 3007, 1, 0, 0, 0, 625, 3013, 1, 0, 0, 0, 627, 3015, 1, 0, 0, 0, 629, 3021, 1, 0, 0, 0, 631, 3023, 1, 0, 0, 0, 633, 3025, 1, 0, 0, 0, 635, 3027, 1, 0, 0, 0, 637, 3029, 1, 0, 0, 0, 639, 3031, 1, 0, 0, 0, 641, 3033, 1, 0, 0, 0, 643, 3035, 1, 0, 0, 0, 645, 3037, 1, 0, 0, 0, 647, 3039, 1, 0, 0, 0, 649, 3042, 1, 0, 0, 0, 651, 3044, 1, 0, 0, 0, 653, 3046, 1, 0, 0, 0, 655, 3049, 1, 0, 0, 0, 657, 3053, 1, 0, 0, 0, 659, 3086, 1, 0, 0, 0, 661, 3088, 1, 0, 0, 0, 663, 3100, 1, 0, 0, 0, 665, 3107, 1, 0, 0, 0, 667, 3114, 1, 0, 0, 0, 669, 3121, 1, 0, 0, 0, 671, 3135, 1, 0, 0, 0, 673, 3137, 1, 0, 0, 0, 675, 3155, 1, 0, 0, 0, 677, 3173, 1, 0, 0, 0, 679, 3193, 1, 0, 0, 0, 681, 3198, 1, 0, 0, 0, 683, 3202, 1, 0, 0, 0, 685, 3231, 1, 0, 0, 0, 687, 3233, 1, 0, 0, 0, 689, 3242, 1, 0, 0, 0, 691, 3244, 1, 0, 0, 0, 693, 3246, 1, 0, 0, 0, 695, 3265, 1, 0, 0, 0, 697, 3284, 1, 0, 0, 0, 699, 3290, 1, 0, 0, 0, 701, 702, 5, 59, 0, 0, 702, 2, 1, 0, 0, 0, 703, 704, 5, 40, 0, 0, 704, 4, 1, 0, 0, 0, 705, 706, 5, 41, 0, 0, 706, 6, 1, 0, 0, 0, 707, 708, 5, 44, 0, 0, 708, 8, 1, 0, 0, 0, 709, 710, 5, 46, 0, 0, 710, 10, 1, 0, 0, 0, 711, 712, 5, 91, 0, 0, 712, 12, 1, 0, 0, 0, 713, 714, 5, 93, 0, 0, 714, 14, 1, 0, 0, 0, 715, 716, 5, 65, 0, 0, 716, 717, 5, 68, 0, 0, 717, 718, 5, 68, 0, 0, 718, 16, 1, 0, 0, 0, 719, 720, 5, 65, 0, 0, 720, 721, 5, 70, 0, 0, 721, 722, 5, 84, 0, 0, 722, 723, 5, 69, 0, 0, 723, 724, 5, 82, 0, 0, 724, 18, 1, 0, 0, 0, 725, 726, 5, 65, 0, 0, 726, 727, 5, 76, 0, 0, 727, 728, 5, 76, 0, 0, 728, 20, 1, 0, 0, 0, 729, 730, 5, 65, 0, 0, 730, 731, 5, 76, 0, 0, 731, 732, 5, 84, 0, 0, 732, 733, 5, 69, 0, 0, 733, 734, 5, 82, 0, 0, 734, 22, 1, 0, 0, 0, 735, 736, 5, 65, 0, 0, 736, 737, 5, 76, 0, 0, 737, 738, 5, 87, 0, 0, 738, 739, 5, 65, 0, 0, 739, 740, 5, 89, 0, 0, 740, 741, 5, 83, 0, 0, 741, 24, 1, 0, 0, 0, 742, 743, 5, 65, 0, 0, 743, 744, 5, 78, 0, 0, 744, 745, 5, 65, 0, 0, 745, 746, 5, 76, 0, 0, 746, 747, 5, 89, 0, 0, 747, 748, 5, 90, 0, 0, 748, 749, 5, 69, 0, 0, 749, 26, 1, 0, 0, 0, 750, 751, 5, 65, 0, 0, 751, 752, 5, 78, 0, 0, 752, 753, 5, 68, 0, 0, 753, 28, 1, 0, 0, 0, 754, 755, 5, 65, 0, 0, 755, 756, 5, 78, 0, 0, 756, 757, 5, 84, 0, 0, 757, 758, 5, 73, 0, 0, 758, 30, 1, 0, 0, 0, 759, 760, 5, 65, 0, 0, 760, 761, 5, 78, 0, 0, 761, 762, 5, 89, 0, 0, 762, 32, 1, 0, 0, 0, 763, 764, 5, 65, 0, 0, 764, 765, 5, 78, 0, 0, 765, 766, 5, 89, 0, 0, 766, 767, 5, 95, 0, 0, 767, 768, 5, 86, 0, 0, 768, 769, 5, 65, 0, 0, 769, 770, 5, 76, 0, 0, 770, 771, 5, 85, 0, 0, 771, 772, 5, 69, 0, 0, 772, 34, 1, 0, 0, 0, 773, 774, 5, 65, 0, 0, 774, 775, 5, 82, 0, 0, 775, 776, 5, 67, 0, 0, 776, 777, 5, 72, 0, 0, 777, 778, 5, 73, 0, 0, 778, 779, 5, 86, 0, 0, 779, 780, 5, 69, 0, 0, 780, 36, 1, 0, 0, 0, 781, 782, 5, 65, 0, 0, 782, 783, 5, 82, 0, 0, 783, 784, 5, 82, 0, 0, 784, 785, 5, 65, 0, 0, 785, 786, 5, 89, 0, 0, 786, 38, 1, 0, 0, 0, 787, 788, 5, 65, 0, 0, 788, 789, 5, 83, 0, 0, 789, 40, 1, 0, 0, 0, 790, 791, 5, 65, 0, 0, 791, 792, 5, 83, 0, 0, 792, 793, 5, 67, 0, 0, 793, 42, 1, 0, 0, 0, 794, 795, 5, 65, 0, 0, 795, 796, 5, 84, 0, 0, 796, 44, 1, 0, 0, 0, 797, 798, 5, 65, 0, 0, 798, 799, 5, 85, 0, 0, 799, 800, 5, 84, 0, 0, 800, 801, 5, 72, 0, 0, 801, 802, 5, 79, 0, 0, 802, 803, 5, 82, 0, 0, 803, 804, 5, 73, 0, 0, 804, 805, 5, 90, 0, 0, 805, 806, 5, 65, 0, 0, 806, 807, 5, 84, 0, 0, 807, 808, 5, 73, 0, 0, 808, 809, 5, 79, 0, 0, 809, 810, 5, 78, 0, 0, 810, 46, 1, 0, 0, 0, 811, 812, 5, 66, 0, 0, 812, 813, 5, 69, 0, 0, 813, 814, 5, 84, 0, 0, 814, 815, 5, 87, 0, 0, 815, 816, 5, 69, 0, 0, 816, 817, 5, 69, 0, 0, 817, 818, 5, 78, 0, 0, 818, 48, 1, 0, 0, 0, 819, 820, 5, 66, 0, 0, 820, 821, 5, 79, 0, 0, 821, 822, 5, 84, 0, 0, 822, 823, 5, 72, 0, 0, 823, 50, 1, 0, 0, 0, 824, 825, 5, 66, 0, 0, 825, 826, 5, 85, 0, 0, 826, 827, 5, 67, 0, 0, 827, 828, 5, 75, 0, 0, 828, 829, 5, 69, 0, 0, 829, 830, 5, 84, 0, 0, 830, 52, 1, 0, 0, 0, 831, 832, 5, 66, 0, 0, 832, 833, 5, 85, 0, 0, 833, 834, 5, 67, 0, 0, 834, 835, 5, 75, 0, 0, 835, 836, 5, 69, 0, 0, 836, 837, 5, 84, 0, 0, 837, 838, 5, 83, 0, 0, 838, 54, 1, 0, 0, 0, 839, 840, 5, 66, 0, 0, 840, 841, 5, 89, 0, 0, 841, 56, 1, 0, 0, 0, 842, 843, 5, 67, 0, 0, 843, 844, 5, 65, 0, 0, 844, 845, 5, 67, 0, 0, 845, 846, 5, 72, 0, 0, 846, 847, 5, 69, 0, 0, 847, 58, 1, 0, 0, 0, 848, 849, 5, 67, 0, 0, 849, 850, 5, 65, 0, 0, 850, 851, 5, 83, 0, 0, 851, 852, 5, 67, 0, 0, 852, 853, 5, 65, 0, 0, 853, 854, 5, 68, 0, 0, 854, 855, 5, 69, 0, 0, 855, 60, 1, 0, 0, 0, 856, 857, 5, 67, 0, 0, 857, 858, 5, 65, 0, 0, 858, 859, 5, 83, 0, 0, 859, 860, 5, 69, 0, 0, 860, 62, 1, 0, 0, 0, 861, 862, 5, 67, 0, 0, 862, 863, 5, 65, 0, 0, 863, 864, 5, 83, 0, 0, 864, 865, 5, 84, 0, 0, 865, 64, 1, 0, 0, 0, 866, 867, 5, 67, 0, 0, 867, 868, 5, 65, 0, 0, 868, 869, 5, 84, 0, 0, 869, 870, 5, 65, 0, 0, 870, 871, 5, 76, 0, 0, 871, 872, 5, 79, 0, 0, 872, 873, 5, 71, 0, 0, 873, 66, 1, 0, 0, 0, 874, 875, 5, 67, 0, 0, 875, 876, 5, 65, 0, 0, 876, 877, 5, 84, 0, 0, 877, 878, 5, 65, 0, 0, 878, 879, 5, 76, 0, 0, 879, 880, 5, 79, 0, 0, 880, 881, 5, 71, 0, 0, 881, 882, 5, 83, 0, 0, 882, 68, 1, 0, 0, 0, 883, 884, 5, 67, 0, 0, 884, 885, 5, 72, 0, 0, 885, 886, 5, 65, 0, 0, 886, 887, 5, 78, 0, 0, 887, 888, 5, 71, 0, 0, 888, 889, 5, 69, 0, 0, 889, 70, 1, 0, 0, 0, 890, 891, 5, 67, 0, 0, 891, 892, 5, 72, 0, 0, 892, 893, 5, 69, 0, 0, 893, 894, 5, 67, 0, 0, 894, 895, 5, 75, 0, 0, 895, 72, 1, 0, 0, 0, 896, 897, 5, 67, 0, 0, 897, 898, 5, 76, 0, 0, 898, 899, 5, 69, 0, 0, 899, 900, 5, 65, 0, 0, 900, 901, 5, 82, 0, 0, 901, 74, 1, 0, 0, 0, 902, 903, 5, 67, 0, 0, 903, 904, 5, 76, 0, 0, 904, 905, 5, 85, 0, 0, 905, 906, 5, 83, 0, 0, 906, 907, 5, 84, 0, 0, 907, 908, 5, 69, 0, 0, 908, 909, 5, 82, 0, 0, 909, 76, 1, 0, 0, 0, 910, 911, 5, 67, 0, 0, 911, 912, 5, 76, 0, 0, 912, 913, 5, 85, 0, 0, 913, 914, 5, 83, 0, 0, 914, 915, 5, 84, 0, 0, 915, 916, 5, 69, 0, 0, 916, 917, 5, 82, 0, 0, 917, 918, 5, 69, 0, 0, 918, 919, 5, 68, 0, 0, 919, 78, 1, 0, 0, 0, 920, 921, 5, 67, 0, 0, 921, 922, 5, 79, 0, 0, 922, 923, 5, 68, 0, 0, 923, 924, 5, 69, 0, 0, 924, 925, 5, 71, 0, 0, 925, 926, 5, 69, 0, 0, 926, 927, 5, 78, 0, 0, 927, 80, 1, 0, 0, 0, 928, 929, 5, 67, 0, 0, 929, 930, 5, 79, 0, 0, 930, 931, 5, 76, 0, 0, 931, 932, 5, 76, 0, 0, 932, 933, 5, 65, 0, 0, 933, 934, 5, 84, 0, 0, 934, 935, 5, 69, 0, 0, 935, 82, 1, 0, 0, 0, 936, 937, 5, 67, 0, 0, 937, 938, 5, 79, 0, 0, 938, 939, 5, 76, 0, 0, 939, 940, 5, 76, 0, 0, 940, 941, 5, 69, 0, 0, 941, 942, 5, 67, 0, 0, 942, 943, 5, 84, 0, 0, 943, 944, 5, 73, 0, 0, 944, 945, 5, 79, 0, 0, 945, 946, 5, 78, 0, 0, 946, 84, 1, 0, 0, 0, 947, 948, 5, 67, 0, 0, 948, 949, 5, 79, 0, 0, 949, 950, 5, 76, 0, 0, 950, 951, 5, 85, 0, 0, 951, 952, 5, 77, 0, 0, 952, 953, 5, 78, 0, 0, 953, 86, 1, 0, 0, 0, 954, 955, 5, 67, 0, 0, 955, 956, 5, 79, 0, 0, 956, 957, 5, 76, 0, 0, 957, 958, 5, 85, 0, 0, 958, 959, 5, 77, 0, 0, 959, 960, 5, 78, 0, 0, 960, 961, 5, 83, 0, 0, 961, 88, 1, 0, 0, 0, 962, 963, 5, 67, 0, 0, 963, 964, 5, 79, 0, 0, 964, 965, 5, 77, 0, 0, 965, 966, 5, 77, 0, 0, 966, 967, 5, 69, 0, 0, 967, 968, 5, 78, 0, 0, 968, 969, 5, 84, 0, 0, 969, 90, 1, 0, 0, 0, 970, 971, 5, 67, 0, 0, 971, 972, 5, 79, 0, 0, 972, 973, 5, 77, 0, 0, 973, 974, 5, 77, 0, 0, 974, 975, 5, 73, 0, 0, 975, 976, 5, 84, 0, 0, 976, 92, 1, 0, 0, 0, 977, 978, 5, 67, 0, 0, 978, 979, 5, 79, 0, 0, 979, 980, 5, 77, 0, 0, 980, 981, 5, 80, 0, 0, 981, 982, 5, 65, 0, 0, 982, 983, 5, 67, 0, 0, 983, 984, 5, 84, 0, 0, 984, 94, 1, 0, 0, 0, 985, 986, 5, 67, 0, 0, 986, 987, 5, 79, 0, 0, 987, 988, 5, 77, 0, 0, 988, 989, 5, 80, 0, 0, 989, 990, 5, 65, 0, 0, 990, 991, 5, 67, 0, 0, 991, 992, 5, 84, 0, 0, 992, 993, 5, 73, 0, 0, 993, 994, 5, 79, 0, 0, 994, 995, 5, 78, 0, 0, 995, 996, 5, 83, 0, 0, 996, 96, 1, 0, 0, 0, 997, 998, 5, 67, 0, 0, 998, 999, 5, 79, 0, 0, 999, 1000, 5, 77, 0, 0, 1000, 1001, 5, 80, 0, 0, 1001, 1002, 5, 85, 0, 0, 1002, 1003, 5, 84, 0, 0, 1003, 1004, 5, 69, 0, 0, 1004, 98, 1, 0, 0, 0, 1005, 1006, 5, 67, 0, 0, 1006, 1007, 5, 79, 0, 0, 1007, 1008, 5, 78, 0, 0, 1008, 1009, 5, 67, 0, 0, 1009, 1010, 5, 65, 0, 0, 1010, 1011, 5, 84, 0, 0, 1011, 1012, 5, 69, 0, 0, 1012, 1013, 5, 78, 0, 0, 1013, 1014, 5, 65, 0, 0, 1014, 1015, 5, 84, 0, 0, 1015, 1016, 5, 69, 0, 0, 1016, 100, 1, 0, 0, 0, 1017, 1018, 5, 67, 0, 0, 1018, 1019, 5, 79, 0, 0, 1019, 1020, 5, 78, 0, 0, 1020, 1021, 5, 83, 0, 0, 1021, 1022, 5, 84, 0, 0, 1022, 1023, 5, 82, 0, 0, 1023, 1024, 5, 65, 0, 0, 1024, 1025, 5, 73, 0, 0, 1025, 1026, 5, 78, 0, 0, 1026, 1027, 5, 84, 0, 0, 1027, 102, 1, 0, 0, 0, 1028, 1029, 5, 67, 0, 0, 1029, 1030, 5, 79, 0, 0, 1030, 1031, 5, 83, 0, 0, 1031, 1032, 5, 84, 0, 0, 1032, 104, 1, 0, 0, 0, 1033, 1034, 5, 67, 0, 0, 1034, 1035, 5, 82, 0, 0, 1035, 1036, 5, 69, 0, 0, 1036, 1037, 5, 65, 0, 0, 1037, 1038, 5, 84, 0, 0, 1038, 1039, 5, 69, 0, 0, 1039, 106, 1, 0, 0, 0, 1040, 1041, 5, 67, 0, 0, 1041, 1042, 5, 82, 0, 0, 1042, 1043, 5, 79, 0, 0, 1043, 1044, 5, 83, 0, 0, 1044, 1045, 5, 83, 0, 0, 1045, 108, 1, 0, 0, 0, 1046, 1047, 5, 67, 0, 0, 1047, 1048, 5, 85, 0, 0, 1048, 1049, 5, 66, 0, 0, 1049, 1050, 5, 69, 0, 0, 1050, 110, 1, 0, 0, 0, 1051, 1052, 5, 67, 0, 0, 1052, 1053, 5, 85, 0, 0, 1053, 1054, 5, 82, 0, 0, 1054, 1055, 5, 82, 0, 0, 1055, 1056, 5, 69, 0, 0, 1056, 1057, 5, 78, 0, 0, 1057, 1058, 5, 84, 0, 0, 1058, 112, 1, 0, 0, 0, 1059, 1060, 5, 67, 0, 0, 1060, 1061, 5, 85, 0, 0, 1061, 1062, 5, 82, 0, 0, 1062, 1063, 5, 82, 0, 0, 1063, 1064, 5, 69, 0, 0, 1064, 1065, 5, 78, 0, 0, 1065, 1066, 5, 84, 0, 0, 1066, 1067, 5, 95, 0, 0, 1067, 1068, 5, 68, 0, 0, 1068, 1069, 5, 65, 0, 0, 1069, 1070, 5, 84, 0, 0, 1070, 1071, 5, 69, 0, 0, 1071, 114, 1, 0, 0, 0, 1072, 1073, 5, 67, 0, 0, 1073, 1074, 5, 85, 0, 0, 1074, 1075, 5, 82, 0, 0, 1075, 1076, 5, 82, 0, 0, 1076, 1077, 5, 69, 0, 0, 1077, 1078, 5, 78, 0, 0, 1078, 1079, 5, 84, 0, 0, 1079, 1080, 5, 95, 0, 0, 1080, 1081, 5, 84, 0, 0, 1081, 1082, 5, 73, 0, 0, 1082, 1083, 5, 77, 0, 0, 1083, 1084, 5, 69, 0, 0, 1084, 116, 1, 0, 0, 0, 1085, 1086, 5, 67, 0, 0, 1086, 1087, 5, 85, 0, 0, 1087, 1088, 5, 82, 0, 0, 1088, 1089, 5, 82, 0, 0, 1089, 1090, 5, 69, 0, 0, 1090, 1091, 5, 78, 0, 0, 1091, 1092, 5, 84, 0, 0, 1092, 1093, 5, 95, 0, 0, 1093, 1094, 5, 84, 0, 0, 1094, 1095, 5, 73, 0, 0, 1095, 1096, 5, 77, 0, 0, 1096, 1097, 5, 69, 0, 0, 1097, 1098, 5, 83, 0, 0, 1098, 1099, 5, 84, 0, 0, 1099, 1100, 5, 65, 0, 0, 1100, 1101, 5, 77, 0, 0, 1101, 1102, 5, 80, 0, 0, 1102, 118, 1, 0, 0, 0, 1103, 1104, 5, 67, 0, 0, 1104, 1105, 5, 85, 0, 0, 1105, 1106, 5, 82, 0, 0, 1106, 1107, 5, 82, 0, 0, 1107, 1108, 5, 69, 0, 0, 1108, 1109, 5, 78, 0, 0, 1109, 1110, 5, 84, 0, 0, 1110, 1111, 5, 95, 0, 0, 1111, 1112, 5, 85, 0, 0, 1112, 1113, 5, 83, 0, 0, 1113, 1114, 5, 69, 0, 0, 1114, 1115, 5, 82, 0, 0, 1115, 120, 1, 0, 0, 0, 1116, 1117, 5, 68, 0, 0, 1117, 1118, 5, 65, 0, 0, 1118, 1119, 5, 89, 0, 0, 1119, 122, 1, 0, 0, 0, 1120, 1121, 5, 68, 0, 0, 1121, 1122, 5, 65, 0, 0, 1122, 1123, 5, 89, 0, 0, 1123, 1124, 5, 83, 0, 0, 1124, 124, 1, 0, 0, 0, 1125, 1126, 5, 68, 0, 0, 1126, 1127, 5, 65, 0, 0, 1127, 1128, 5, 89, 0, 0, 1128, 1129, 5, 79, 0, 0, 1129, 1130, 5, 70, 0, 0, 1130, 1131, 5, 89, 0, 0, 1131, 1132, 5, 69, 0, 0, 1132, 1133, 5, 65, 0, 0, 1133, 1134, 5, 82, 0, 0, 1134, 126, 1, 0, 0, 0, 1135, 1136, 5, 68, 0, 0, 1136, 1137, 5, 65, 0, 0, 1137, 1138, 5, 84, 0, 0, 1138, 1139, 5, 65, 0, 0, 1139, 128, 1, 0, 0, 0, 1140, 1141, 5, 68, 0, 0, 1141, 1142, 5, 65, 0, 0, 1142, 1143, 5, 84, 0, 0, 1143, 1144, 5, 65, 0, 0, 1144, 1145, 5, 66, 0, 0, 1145, 1146, 5, 65, 0, 0, 1146, 1147, 5, 83, 0, 0, 1147, 1148, 5, 69, 0, 0, 1148, 130, 1, 0, 0, 0, 1149, 1150, 5, 68, 0, 0, 1150, 1151, 5, 65, 0, 0, 1151, 1152, 5, 84, 0, 0, 1152, 1153, 5, 65, 0, 0, 1153, 1154, 5, 66, 0, 0, 1154, 1155, 5, 65, 0, 0, 1155, 1156, 5, 83, 0, 0, 1156, 1157, 5, 69, 0, 0, 1157, 1158, 5, 83, 0, 0, 1158, 132, 1, 0, 0, 0, 1159, 1160, 5, 68, 0, 0, 1160, 1161, 5, 65, 0, 0, 1161, 1162, 5, 84, 0, 0, 1162, 1163, 5, 69, 0, 0, 1163, 1164, 5, 65, 0, 0, 1164, 1165, 5, 68, 0, 0, 1165, 1166, 5, 68, 0, 0, 1166, 134, 1, 0, 0, 0, 1167, 1168, 5, 68, 0, 0, 1168, 1169, 5, 65, 0, 0, 1169, 1170, 5, 84, 0, 0, 1170, 1171, 5, 69, 0, 0, 1171, 1172, 5, 68, 0, 0, 1172, 1173, 5, 73, 0, 0, 1173, 1174, 5, 70, 0, 0, 1174, 1175, 5, 70, 0, 0, 1175, 136, 1, 0, 0, 0, 1176, 1177, 5, 68, 0, 0, 1177, 1178, 5, 66, 0, 0, 1178, 1179, 5, 80, 0, 0, 1179, 1180, 5, 82, 0, 0, 1180, 1181, 5, 79, 0, 0, 1181, 1182, 5, 80, 0, 0, 1182, 1183, 5, 69, 0, 0, 1183, 1184, 5, 82, 0, 0, 1184, 1185, 5, 84, 0, 0, 1185, 1186, 5, 73, 0, 0, 1186, 1187, 5, 69, 0, 0, 1187, 1188, 5, 83, 0, 0, 1188, 138, 1, 0, 0, 0, 1189, 1190, 5, 68, 0, 0, 1190, 1191, 5, 69, 0, 0, 1191, 1192, 5, 70, 0, 0, 1192, 1193, 5, 65, 0, 0, 1193, 1194, 5, 85, 0, 0, 1194, 1195, 5, 76, 0, 0, 1195, 1196, 5, 84, 0, 0, 1196, 140, 1, 0, 0, 0, 1197, 1198, 5, 68, 0, 0, 1198, 1199, 5, 69, 0, 0, 1199, 1200, 5, 70, 0, 0, 1200, 1201, 5, 73, 0, 0, 1201, 1202, 5, 78, 0, 0, 1202, 1203, 5, 69, 0, 0, 1203, 1204, 5, 68, 0, 0, 1204, 142, 1, 0, 0, 0, 1205, 1206, 5, 68, 0, 0, 1206, 1207, 5, 69, 0, 0, 1207, 1208, 5, 76, 0, 0, 1208, 1209, 5, 69, 0, 0, 1209, 1210, 5, 84, 0, 0, 1210, 1211, 5, 69, 0, 0, 1211, 144, 1, 0, 0, 0, 1212, 1213, 5, 68, 0, 0, 1213, 1214, 5, 69, 0, 0, 1214, 1215, 5, 76, 0, 0, 1215, 1216, 5, 73, 0, 0, 1216, 1217, 5, 77, 0, 0, 1217, 1218, 5, 73, 0, 0, 1218, 1219, 5, 84, 0, 0, 1219, 1220, 5, 69, 0, 0, 1220, 1221, 5, 68, 0, 0, 1221, 146, 1, 0, 0, 0, 1222, 1223, 5, 68, 0, 0, 1223, 1224, 5, 69, 0, 0, 1224, 1225, 5, 83, 0, 0, 1225, 1226, 5, 67, 0, 0, 1226, 148, 1, 0, 0, 0, 1227, 1228, 5, 68, 0, 0, 1228, 1229, 5, 69, 0, 0, 1229, 1230, 5, 83, 0, 0, 1230, 1231, 5, 67, 0, 0, 1231, 1232, 5, 82, 0, 0, 1232, 1233, 5, 73, 0, 0, 1233, 1234, 5, 66, 0, 0, 1234, 1235, 5, 69, 0, 0, 1235, 150, 1, 0, 0, 0, 1236, 1237, 5, 68, 0, 0, 1237, 1238, 5, 70, 0, 0, 1238, 1239, 5, 83, 0, 0, 1239, 152, 1, 0, 0, 0, 1240, 1241, 5, 68, 0, 0, 1241, 1242, 5, 73, 0, 0, 1242, 1243, 5, 82, 0, 0, 1243, 1244, 5, 69, 0, 0, 1244, 1245, 5, 67, 0, 0, 1245, 1246, 5, 84, 0, 0, 1246, 1247, 5, 79, 0, 0, 1247, 1248, 5, 82, 0, 0, 1248, 1249, 5, 73, 0, 0, 1249, 1250, 5, 69, 0, 0, 1250, 1251, 5, 83, 0, 0, 1251, 154, 1, 0, 0, 0, 1252, 1253, 5, 68, 0, 0, 1253, 1254, 5, 73, 0, 0, 1254, 1255, 5, 82, 0, 0, 1255, 1256, 5, 69, 0, 0, 1256, 1257, 5, 67, 0, 0, 1257, 1258, 5, 84, 0, 0, 1258, 1259, 5, 79, 0, 0, 1259, 1260, 5, 82, 0, 0, 1260, 1261, 5, 89, 0, 0, 1261, 156, 1, 0, 0, 0, 1262, 1263, 5, 68, 0, 0, 1263, 1264, 5, 73, 0, 0, 1264, 1265, 5, 83, 0, 0, 1265, 1266, 5, 84, 0, 0, 1266, 1267, 5, 73, 0, 0, 1267, 1268, 5, 78, 0, 0, 1268, 1269, 5, 67, 0, 0, 1269, 1270, 5, 84, 0, 0, 1270, 158, 1, 0, 0, 0, 1271, 1272, 5, 68, 0, 0, 1272, 1273, 5, 73, 0, 0, 1273, 1274, 5, 83, 0, 0, 1274, 1275, 5, 84, 0, 0, 1275, 1276, 5, 82, 0, 0, 1276, 1277, 5, 73, 0, 0, 1277, 1278, 5, 66, 0, 0, 1278, 1279, 5, 85, 0, 0, 1279, 1280, 5, 84, 0, 0, 1280, 1281, 5, 69, 0, 0, 1281, 160, 1, 0, 0, 0, 1282, 1283, 5, 68, 0, 0, 1283, 1284, 5, 73, 0, 0, 1284, 1285, 5, 86, 0, 0, 1285, 162, 1, 0, 0, 0, 1286, 1287, 5, 68, 0, 0, 1287, 1288, 5, 82, 0, 0, 1288, 1289, 5, 79, 0, 0, 1289, 1290, 5, 80, 0, 0, 1290, 164, 1, 0, 0, 0, 1291, 1292, 5, 69, 0, 0, 1292, 1293, 5, 76, 0, 0, 1293, 1294, 5, 83, 0, 0, 1294, 1295, 5, 69, 0, 0, 1295, 166, 1, 0, 0, 0, 1296, 1297, 5, 69, 0, 0, 1297, 1298, 5, 78, 0, 0, 1298, 1299, 5, 68, 0, 0, 1299, 168, 1, 0, 0, 0, 1300, 1301, 5, 69, 0, 0, 1301, 1302, 5, 83, 0, 0, 1302, 1303, 5, 67, 0, 0, 1303, 1304, 5, 65, 0, 0, 1304, 1305, 5, 80, 0, 0, 1305, 1306, 5, 69, 0, 0, 1306, 170, 1, 0, 0, 0, 1307, 1308, 5, 69, 0, 0, 1308, 1309, 5, 83, 0, 0, 1309, 1310, 5, 67, 0, 0, 1310, 1311, 5, 65, 0, 0, 1311, 1312, 5, 80, 0, 0, 1312, 1313, 5, 69, 0, 0, 1313, 1314, 5, 68, 0, 0, 1314, 172, 1, 0, 0, 0, 1315, 1316, 5, 69, 0, 0, 1316, 1317, 5, 88, 0, 0, 1317, 1318, 5, 67, 0, 0, 1318, 1319, 5, 69, 0, 0, 1319, 1320, 5, 80, 0, 0, 1320, 1321, 5, 84, 0, 0, 1321, 174, 1, 0, 0, 0, 1322, 1323, 5, 69, 0, 0, 1323, 1324, 5, 88, 0, 0, 1324, 1325, 5, 67, 0, 0, 1325, 1326, 5, 72, 0, 0, 1326, 1327, 5, 65, 0, 0, 1327, 1328, 5, 78, 0, 0, 1328, 1329, 5, 71, 0, 0, 1329, 1330, 5, 69, 0, 0, 1330, 176, 1, 0, 0, 0, 1331, 1332, 5, 69, 0, 0, 1332, 1333, 5, 88, 0, 0, 1333, 1334, 5, 67, 0, 0, 1334, 1335, 5, 76, 0, 0, 1335, 1336, 5, 85, 0, 0, 1336, 1337, 5, 68, 0, 0, 1337, 1338, 5, 69, 0, 0, 1338, 178, 1, 0, 0, 0, 1339, 1340, 5, 69, 0, 0, 1340, 1341, 5, 88, 0, 0, 1341, 1342, 5, 73, 0, 0, 1342, 1343, 5, 83, 0, 0, 1343, 1344, 5, 84, 0, 0, 1344, 1345, 5, 83, 0, 0, 1345, 180, 1, 0, 0, 0, 1346, 1347, 5, 69, 0, 0, 1347, 1348, 5, 88, 0, 0, 1348, 1349, 5, 80, 0, 0, 1349, 1350, 5, 76, 0, 0, 1350, 1351, 5, 65, 0, 0, 1351, 1352, 5, 73, 0, 0, 1352, 1353, 5, 78, 0, 0, 1353, 182, 1, 0, 0, 0, 1354, 1355, 5, 69, 0, 0, 1355, 1356, 5, 88, 0, 0, 1356, 1357, 5, 80, 0, 0, 1357, 1358, 5, 79, 0, 0, 1358, 1359, 5, 82, 0, 0, 1359, 1360, 5, 84, 0, 0, 1360, 184, 1, 0, 0, 0, 1361, 1362, 5, 69, 0, 0, 1362, 1363, 5, 88, 0, 0, 1363, 1364, 5, 84, 0, 0, 1364, 1365, 5, 69, 0, 0, 1365, 1366, 5, 78, 0, 0, 1366, 1367, 5, 68, 0, 0, 1367, 1368, 5, 69, 0, 0, 1368, 1369, 5, 68, 0, 0, 1369, 186, 1, 0, 0, 0, 1370, 1371, 5, 69, 0, 0, 1371, 1372, 5, 88, 0, 0, 1372, 1373, 5, 84, 0, 0, 1373, 1374, 5, 69, 0, 0, 1374, 1375, 5, 82, 0, 0, 1375, 1376, 5, 78, 0, 0, 1376, 1377, 5, 65, 0, 0, 1377, 1378, 5, 76, 0, 0, 1378, 188, 1, 0, 0, 0, 1379, 1380, 5, 69, 0, 0, 1380, 1381, 5, 88, 0, 0, 1381, 1382, 5, 84, 0, 0, 1382, 1383, 5, 82, 0, 0, 1383, 1384, 5, 65, 0, 0, 1384, 1385, 5, 67, 0, 0, 1385, 1386, 5, 84, 0, 0, 1386, 190, 1, 0, 0, 0, 1387, 1388, 5, 70, 0, 0, 1388, 1389, 5, 65, 0, 0, 1389, 1390, 5, 76, 0, 0, 1390, 1391, 5, 83, 0, 0, 1391, 1392, 5, 69, 0, 0, 1392, 192, 1, 0, 0, 0, 1393, 1394, 5, 70, 0, 0, 1394, 1395, 5, 69, 0, 0, 1395, 1396, 5, 84, 0, 0, 1396, 1397, 5, 67, 0, 0, 1397, 1398, 5, 72, 0, 0, 1398, 194, 1, 0, 0, 0, 1399, 1400, 5, 70, 0, 0, 1400, 1401, 5, 73, 0, 0, 1401, 1402, 5, 69, 0, 0, 1402, 1403, 5, 76, 0, 0, 1403, 1404, 5, 68, 0, 0, 1404, 1405, 5, 83, 0, 0, 1405, 196, 1, 0, 0, 0, 1406, 1407, 5, 70, 0, 0, 1407, 1408, 5, 73, 0, 0, 1408, 1409, 5, 76, 0, 0, 1409, 1410, 5, 84, 0, 0, 1410, 1411, 5, 69, 0, 0, 1411, 1412, 5, 82, 0, 0, 1412, 198, 1, 0, 0, 0, 1413, 1414, 5, 70, 0, 0, 1414, 1415, 5, 73, 0, 0, 1415, 1416, 5, 76, 0, 0, 1416, 1417, 5, 69, 0, 0, 1417, 1418, 5, 70, 0, 0, 1418, 1419, 5, 79, 0, 0, 1419, 1420, 5, 82, 0, 0, 1420, 1421, 5, 77, 0, 0, 1421, 1422, 5, 65, 0, 0, 1422, 1423, 5, 84, 0, 0, 1423, 200, 1, 0, 0, 0, 1424, 1425, 5, 70, 0, 0, 1425, 1426, 5, 73, 0, 0, 1426, 1427, 5, 82, 0, 0, 1427, 1428, 5, 83, 0, 0, 1428, 1429, 5, 84, 0, 0, 1429, 202, 1, 0, 0, 0, 1430, 1431, 5, 70, 0, 0, 1431, 1432, 5, 79, 0, 0, 1432, 1433, 5, 76, 0, 0, 1433, 1434, 5, 76, 0, 0, 1434, 1435, 5, 79, 0, 0, 1435, 1436, 5, 87, 0, 0, 1436, 1437, 5, 73, 0, 0, 1437, 1438, 5, 78, 0, 0, 1438, 1439, 5, 71, 0, 0, 1439, 204, 1, 0, 0, 0, 1440, 1441, 5, 70, 0, 0, 1441, 1442, 5, 79, 0, 0, 1442, 1443, 5, 82, 0, 0, 1443, 206, 1, 0, 0, 0, 1444, 1445, 5, 70, 0, 0, 1445, 1446, 5, 79, 0, 0, 1446, 1447, 5, 82, 0, 0, 1447, 1448, 5, 69, 0, 0, 1448, 1449, 5, 73, 0, 0, 1449, 1450, 5, 71, 0, 0, 1450, 1451, 5, 78, 0, 0, 1451, 208, 1, 0, 0, 0, 1452, 1453, 5, 70, 0, 0, 1453, 1454, 5, 79, 0, 0, 1454, 1455, 5, 82, 0, 0, 1455, 1456, 5, 77, 0, 0, 1456, 1457, 5, 65, 0, 0, 1457, 1458, 5, 84, 0, 0, 1458, 210, 1, 0, 0, 0, 1459, 1460, 5, 70, 0, 0, 1460, 1461, 5, 79, 0, 0, 1461, 1462, 5, 82, 0, 0, 1462, 1463, 5, 77, 0, 0, 1463, 1464, 5, 65, 0, 0, 1464, 1465, 5, 84, 0, 0, 1465, 1466, 5, 84, 0, 0, 1466, 1467, 5, 69, 0, 0, 1467, 1468, 5, 68, 0, 0, 1468, 212, 1, 0, 0, 0, 1469, 1470, 5, 70, 0, 0, 1470, 1471, 5, 82, 0, 0, 1471, 1472, 5, 79, 0, 0, 1472, 1473, 5, 77, 0, 0, 1473, 214, 1, 0, 0, 0, 1474, 1475, 5, 70, 0, 0, 1475, 1476, 5, 85, 0, 0, 1476, 1477, 5, 76, 0, 0, 1477, 1478, 5, 76, 0, 0, 1478, 216, 1, 0, 0, 0, 1479, 1480, 5, 70, 0, 0, 1480, 1481, 5, 85, 0, 0, 1481, 1482, 5, 78, 0, 0, 1482, 1483, 5, 67, 0, 0, 1483, 1484, 5, 84, 0, 0, 1484, 1485, 5, 73, 0, 0, 1485, 1486, 5, 79, 0, 0, 1486, 1487, 5, 78, 0, 0, 1487, 218, 1, 0, 0, 0, 1488, 1489, 5, 70, 0, 0, 1489, 1490, 5, 85, 0, 0, 1490, 1491, 5, 78, 0, 0, 1491, 1492, 5, 67, 0, 0, 1492, 1493, 5, 84, 0, 0, 1493, 1494, 5, 73, 0, 0, 1494, 1495, 5, 79, 0, 0, 1495, 1496, 5, 78, 0, 0, 1496, 1497, 5, 83, 0, 0, 1497, 220, 1, 0, 0, 0, 1498, 1499, 5, 71, 0, 0, 1499, 1500, 5, 69, 0, 0, 1500, 1501, 5, 78, 0, 0, 1501, 1502, 5, 69, 0, 0, 1502, 1503, 5, 82, 0, 0, 1503, 1504, 5, 65, 0, 0, 1504, 1505, 5, 84, 0, 0, 1505, 1506, 5, 69, 0, 0, 1506, 1507, 5, 68, 0, 0, 1507, 222, 1, 0, 0, 0, 1508, 1509, 5, 71, 0, 0, 1509, 1510, 5, 76, 0, 0, 1510, 1511, 5, 79, 0, 0, 1511, 1512, 5, 66, 0, 0, 1512, 1513, 5, 65, 0, 0, 1513, 1514, 5, 76, 0, 0, 1514, 224, 1, 0, 0, 0, 1515, 1516, 5, 71, 0, 0, 1516, 1517, 5, 82, 0, 0, 1517, 1518, 5, 65, 0, 0, 1518, 1519, 5, 78, 0, 0, 1519, 1520, 5, 84, 0, 0, 1520, 226, 1, 0, 0, 0, 1521, 1522, 5, 71, 0, 0, 1522, 1523, 5, 82, 0, 0, 1523, 1524, 5, 79, 0, 0, 1524, 1525, 5, 85, 0, 0, 1525, 1526, 5, 80, 0, 0, 1526, 228, 1, 0, 0, 0, 1527, 1528, 5, 71, 0, 0, 1528, 1529, 5, 82, 0, 0, 1529, 1530, 5, 79, 0, 0, 1530, 1531, 5, 85, 0, 0, 1531, 1532, 5, 80, 0, 0, 1532, 1533, 5, 73, 0, 0, 1533, 1534, 5, 78, 0, 0, 1534, 1535, 5, 71, 0, 0, 1535, 230, 1, 0, 0, 0, 1536, 1537, 5, 72, 0, 0, 1537, 1538, 5, 65, 0, 0, 1538, 1539, 5, 86, 0, 0, 1539, 1540, 5, 73, 0, 0, 1540, 1541, 5, 78, 0, 0, 1541, 1542, 5, 71, 0, 0, 1542, 232, 1, 0, 0, 0, 1543, 1544, 5, 72, 0, 0, 1544, 1545, 5, 79, 0, 0, 1545, 1546, 5, 85, 0, 0, 1546, 1547, 5, 82, 0, 0, 1547, 234, 1, 0, 0, 0, 1548, 1549, 5, 72, 0, 0, 1549, 1550, 5, 79, 0, 0, 1550, 1551, 5, 85, 0, 0, 1551, 1552, 5, 82, 0, 0, 1552, 1553, 5, 83, 0, 0, 1553, 236, 1, 0, 0, 0, 1554, 1555, 5, 73, 0, 0, 1555, 1556, 5, 70, 0, 0, 1556, 238, 1, 0, 0, 0, 1557, 1558, 5, 73, 0, 0, 1558, 1559, 5, 71, 0, 0, 1559, 1560, 5, 78, 0, 0, 1560, 1561, 5, 79, 0, 0, 1561, 1562, 5, 82, 0, 0, 1562, 1563, 5, 69, 0, 0, 1563, 240, 1, 0, 0, 0, 1564, 1565, 5, 73, 0, 0, 1565, 1566, 5, 77, 0, 0, 1566, 1567, 5, 80, 0, 0, 1567, 1568, 5, 79, 0, 0, 1568, 1569, 5, 82, 0, 0, 1569, 1570, 5, 84, 0, 0, 1570, 242, 1, 0, 0, 0, 1571, 1572, 5, 73, 0, 0, 1572, 1573, 5, 78, 0, 0, 1573, 244, 1, 0, 0, 0, 1574, 1575, 5, 73, 0, 0, 1575, 1576, 5, 78, 0, 0, 1576, 1577, 5, 67, 0, 0, 1577, 1578, 5, 76, 0, 0, 1578, 1579, 5, 85, 0, 0, 1579, 1580, 5, 68, 0, 0, 1580, 1581, 5, 69, 0, 0, 1581, 246, 1, 0, 0, 0, 1582, 1583, 5, 73, 0, 0, 1583, 1584, 5, 78, 0, 0, 1584, 1585, 5, 68, 0, 0, 1585, 1586, 5, 69, 0, 0, 1586, 1587, 5, 88, 0, 0, 1587, 248, 1, 0, 0, 0, 1588, 1589, 5, 73, 0, 0, 1589, 1590, 5, 78, 0, 0, 1590, 1591, 5, 68, 0, 0, 1591, 1592, 5, 69, 0, 0, 1592, 1593, 5, 88, 0, 0, 1593, 1594, 5, 69, 0, 0, 1594, 1595, 5, 83, 0, 0, 1595, 250, 1, 0, 0, 0, 1596, 1597, 5, 73, 0, 0, 1597, 1598, 5, 78, 0, 0, 1598, 1599, 5, 78, 0, 0, 1599, 1600, 5, 69, 0, 0, 1600, 1601, 5, 82, 0, 0, 1601, 252, 1, 0, 0, 0, 1602, 1603, 5, 73, 0, 0, 1603, 1604, 5, 78, 0, 0, 1604, 1605, 5, 80, 0, 0, 1605, 1606, 5, 65, 0, 0, 1606, 1607, 5, 84, 0, 0, 1607, 1608, 5, 72, 0, 0, 1608, 254, 1, 0, 0, 0, 1609, 1610, 5, 73, 0, 0, 1610, 1611, 5, 78, 0, 0, 1611, 1612, 5, 80, 0, 0, 1612, 1613, 5, 85, 0, 0, 1613, 1614, 5, 84, 0, 0, 1614, 1615, 5, 70, 0, 0, 1615, 1616, 5, 79, 0, 0, 1616, 1617, 5, 82, 0, 0, 1617, 1618, 5, 77, 0, 0, 1618, 1619, 5, 65, 0, 0, 1619, 1620, 5, 84, 0, 0, 1620, 256, 1, 0, 0, 0, 1621, 1622, 5, 73, 0, 0, 1622, 1623, 5, 78, 0, 0, 1623, 1624, 5, 83, 0, 0, 1624, 1625, 5, 69, 0, 0, 1625, 1626, 5, 82, 0, 0, 1626, 1627, 5, 84, 0, 0, 1627, 258, 1, 0, 0, 0, 1628, 1629, 5, 73, 0, 0, 1629, 1630, 5, 78, 0, 0, 1630, 1631, 5, 84, 0, 0, 1631, 1632, 5, 69, 0, 0, 1632, 1633, 5, 82, 0, 0, 1633, 1634, 5, 83, 0, 0, 1634, 1635, 5, 69, 0, 0, 1635, 1636, 5, 67, 0, 0, 1636, 1637, 5, 84, 0, 0, 1637, 260, 1, 0, 0, 0, 1638, 1639, 5, 73, 0, 0, 1639, 1640, 5, 78, 0, 0, 1640, 1641, 5, 84, 0, 0, 1641, 1642, 5, 69, 0, 0, 1642, 1643, 5, 82, 0, 0, 1643, 1644, 5, 86, 0, 0, 1644, 1645, 5, 65, 0, 0, 1645, 1646, 5, 76, 0, 0, 1646, 262, 1, 0, 0, 0, 1647, 1648, 5, 73, 0, 0, 1648, 1649, 5, 78, 0, 0, 1649, 1650, 5, 84, 0, 0, 1650, 1651, 5, 79, 0, 0, 1651, 264, 1, 0, 0, 0, 1652, 1653, 5, 73, 0, 0, 1653, 1654, 5, 83, 0, 0, 1654, 266, 1, 0, 0, 0, 1655, 1656, 5, 73, 0, 0, 1656, 1657, 5, 84, 0, 0, 1657, 1658, 5, 69, 0, 0, 1658, 1659, 5, 77, 0, 0, 1659, 1660, 5, 83, 0, 0, 1660, 268, 1, 0, 0, 0, 1661, 1662, 5, 74, 0, 0, 1662, 1663, 5, 79, 0, 0, 1663, 1664, 5, 73, 0, 0, 1664, 1665, 5, 78, 0, 0, 1665, 270, 1, 0, 0, 0, 1666, 1667, 5, 75, 0, 0, 1667, 1668, 5, 69, 0, 0, 1668, 1669, 5, 89, 0, 0, 1669, 1670, 5, 83, 0, 0, 1670, 272, 1, 0, 0, 0, 1671, 1672, 5, 76, 0, 0, 1672, 1673, 5, 65, 0, 0, 1673, 1674, 5, 83, 0, 0, 1674, 1675, 5, 84, 0, 0, 1675, 274, 1, 0, 0, 0, 1676, 1677, 5, 76, 0, 0, 1677, 1678, 5, 65, 0, 0, 1678, 1679, 5, 84, 0, 0, 1679, 1680, 5, 69, 0, 0, 1680, 1681, 5, 82, 0, 0, 1681, 1682, 5, 65, 0, 0, 1682, 1683, 5, 76, 0, 0, 1683, 276, 1, 0, 0, 0, 1684, 1685, 5, 76, 0, 0, 1685, 1686, 5, 65, 0, 0, 1686, 1687, 5, 90, 0, 0, 1687, 1688, 5, 89, 0, 0, 1688, 278, 1, 0, 0, 0, 1689, 1690, 5, 76, 0, 0, 1690, 1691, 5, 69, 0, 0, 1691, 1692, 5, 65, 0, 0, 1692, 1693, 5, 68, 0, 0, 1693, 1694, 5, 73, 0, 0, 1694, 1695, 5, 78, 0, 0, 1695, 1696, 5, 71, 0, 0, 1696, 280, 1, 0, 0, 0, 1697, 1698, 5, 76, 0, 0, 1698, 1699, 5, 69, 0, 0, 1699, 1700, 5, 70, 0, 0, 1700, 1701, 5, 84, 0, 0, 1701, 282, 1, 0, 0, 0, 1702, 1703, 5, 76, 0, 0, 1703, 1704, 5, 73, 0, 0, 1704, 1705, 5, 75, 0, 0, 1705, 1706, 5, 69, 0, 0, 1706, 284, 1, 0, 0, 0, 1707, 1708, 5, 73, 0, 0, 1708, 1709, 5, 76, 0, 0, 1709, 1710, 5, 73, 0, 0, 1710, 1711, 5, 75, 0, 0, 1711, 1712, 5, 69, 0, 0, 1712, 286, 1, 0, 0, 0, 1713, 1714, 5, 76, 0, 0, 1714, 1715, 5, 73, 0, 0, 1715, 1716, 5, 77, 0, 0, 1716, 1717, 5, 73, 0, 0, 1717, 1718, 5, 84, 0, 0, 1718, 288, 1, 0, 0, 0, 1719, 1720, 5, 76, 0, 0, 1720, 1721, 5, 73, 0, 0, 1721, 1722, 5, 78, 0, 0, 1722, 1723, 5, 69, 0, 0, 1723, 1724, 5, 83, 0, 0, 1724, 290, 1, 0, 0, 0, 1725, 1726, 5, 76, 0, 0, 1726, 1727, 5, 73, 0, 0, 1727, 1728, 5, 83, 0, 0, 1728, 1729, 5, 84, 0, 0, 1729, 292, 1, 0, 0, 0, 1730, 1731, 5, 76, 0, 0, 1731, 1732, 5, 79, 0, 0, 1732, 1733, 5, 65, 0, 0, 1733, 1734, 5, 68, 0, 0, 1734, 294, 1, 0, 0, 0, 1735, 1736, 5, 76, 0, 0, 1736, 1737, 5, 79, 0, 0, 1737, 1738, 5, 67, 0, 0, 1738, 1739, 5, 65, 0, 0, 1739, 1740, 5, 76, 0, 0, 1740, 296, 1, 0, 0, 0, 1741, 1742, 5, 76, 0, 0, 1742, 1743, 5, 79, 0, 0, 1743, 1744, 5, 67, 0, 0, 1744, 1745, 5, 65, 0, 0, 1745, 1746, 5, 84, 0, 0, 1746, 1747, 5, 73, 0, 0, 1747, 1748, 5, 79, 0, 0, 1748, 1749, 5, 78, 0, 0, 1749, 298, 1, 0, 0, 0, 1750, 1751, 5, 76, 0, 0, 1751, 1752, 5, 79, 0, 0, 1752, 1753, 5, 67, 0, 0, 1753, 1754, 5, 75, 0, 0, 1754, 300, 1, 0, 0, 0, 1755, 1756, 5, 76, 0, 0, 1756, 1757, 5, 79, 0, 0, 1757, 1758, 5, 67, 0, 0, 1758, 1759, 5, 75, 0, 0, 1759, 1760, 5, 83, 0, 0, 1760, 302, 1, 0, 0, 0, 1761, 1762, 5, 76, 0, 0, 1762, 1763, 5, 79, 0, 0, 1763, 1764, 5, 71, 0, 0, 1764, 1765, 5, 73, 0, 0, 1765, 1766, 5, 67, 0, 0, 1766, 1767, 5, 65, 0, 0, 1767, 1768, 5, 76, 0, 0, 1768, 304, 1, 0, 0, 0, 1769, 1770, 5, 77, 0, 0, 1770, 1771, 5, 65, 0, 0, 1771, 1772, 5, 67, 0, 0, 1772, 1773, 5, 82, 0, 0, 1773, 1774, 5, 79, 0, 0, 1774, 306, 1, 0, 0, 0, 1775, 1776, 5, 77, 0, 0, 1776, 1777, 5, 65, 0, 0, 1777, 1778, 5, 80, 0, 0, 1778, 308, 1, 0, 0, 0, 1779, 1780, 5, 77, 0, 0, 1780, 1781, 5, 65, 0, 0, 1781, 1782, 5, 84, 0, 0, 1782, 1783, 5, 67, 0, 0, 1783, 1784, 5, 72, 0, 0, 1784, 1785, 5, 69, 0, 0, 1785, 1786, 5, 68, 0, 0, 1786, 310, 1, 0, 0, 0, 1787, 1788, 5, 77, 0, 0, 1788, 1789, 5, 69, 0, 0, 1789, 1790, 5, 82, 0, 0, 1790, 1791, 5, 71, 0, 0, 1791, 1792, 5, 69, 0, 0, 1792, 312, 1, 0, 0, 0, 1793, 1794, 5, 77, 0, 0, 1794, 1795, 5, 73, 0, 0, 1795, 1796, 5, 67, 0, 0, 1796, 1797, 5, 82, 0, 0, 1797, 1798, 5, 79, 0, 0, 1798, 1799, 5, 83, 0, 0, 1799, 1800, 5, 69, 0, 0, 1800, 1801, 5, 67, 0, 0, 1801, 1802, 5, 79, 0, 0, 1802, 1803, 5, 78, 0, 0, 1803, 1804, 5, 68, 0, 0, 1804, 314, 1, 0, 0, 0, 1805, 1806, 5, 77, 0, 0, 1806, 1807, 5, 73, 0, 0, 1807, 1808, 5, 67, 0, 0, 1808, 1809, 5, 82, 0, 0, 1809, 1810, 5, 79, 0, 0, 1810, 1811, 5, 83, 0, 0, 1811, 1812, 5, 69, 0, 0, 1812, 1813, 5, 67, 0, 0, 1813, 1814, 5, 79, 0, 0, 1814, 1815, 5, 78, 0, 0, 1815, 1816, 5, 68, 0, 0, 1816, 1817, 5, 83, 0, 0, 1817, 316, 1, 0, 0, 0, 1818, 1819, 5, 77, 0, 0, 1819, 1820, 5, 73, 0, 0, 1820, 1821, 5, 76, 0, 0, 1821, 1822, 5, 76, 0, 0, 1822, 1823, 5, 73, 0, 0, 1823, 1824, 5, 83, 0, 0, 1824, 1825, 5, 69, 0, 0, 1825, 1826, 5, 67, 0, 0, 1826, 1827, 5, 79, 0, 0, 1827, 1828, 5, 78, 0, 0, 1828, 1829, 5, 68, 0, 0, 1829, 318, 1, 0, 0, 0, 1830, 1831, 5, 77, 0, 0, 1831, 1832, 5, 73, 0, 0, 1832, 1833, 5, 76, 0, 0, 1833, 1834, 5, 76, 0, 0, 1834, 1835, 5, 73, 0, 0, 1835, 1836, 5, 83, 0, 0, 1836, 1837, 5, 69, 0, 0, 1837, 1838, 5, 67, 0, 0, 1838, 1839, 5, 79, 0, 0, 1839, 1840, 5, 78, 0, 0, 1840, 1841, 5, 68, 0, 0, 1841, 1842, 5, 83, 0, 0, 1842, 320, 1, 0, 0, 0, 1843, 1844, 5, 77, 0, 0, 1844, 1845, 5, 73, 0, 0, 1845, 1846, 5, 78, 0, 0, 1846, 1847, 5, 85, 0, 0, 1847, 1848, 5, 84, 0, 0, 1848, 1849, 5, 69, 0, 0, 1849, 322, 1, 0, 0, 0, 1850, 1851, 5, 77, 0, 0, 1851, 1852, 5, 73, 0, 0, 1852, 1853, 5, 78, 0, 0, 1853, 1854, 5, 85, 0, 0, 1854, 1855, 5, 84, 0, 0, 1855, 1856, 5, 69, 0, 0, 1856, 1857, 5, 83, 0, 0, 1857, 324, 1, 0, 0, 0, 1858, 1859, 5, 77, 0, 0, 1859, 1860, 5, 79, 0, 0, 1860, 1861, 5, 78, 0, 0, 1861, 1862, 5, 84, 0, 0, 1862, 1863, 5, 72, 0, 0, 1863, 326, 1, 0, 0, 0, 1864, 1865, 5, 77, 0, 0, 1865, 1866, 5, 79, 0, 0, 1866, 1867, 5, 78, 0, 0, 1867, 1868, 5, 84, 0, 0, 1868, 1869, 5, 72, 0, 0, 1869, 1870, 5, 83, 0, 0, 1870, 328, 1, 0, 0, 0, 1871, 1872, 5, 77, 0, 0, 1872, 1873, 5, 83, 0, 0, 1873, 1874, 5, 67, 0, 0, 1874, 1875, 5, 75, 0, 0, 1875, 330, 1, 0, 0, 0, 1876, 1877, 5, 78, 0, 0, 1877, 1878, 5, 65, 0, 0, 1878, 1879, 5, 77, 0, 0, 1879, 1880, 5, 69, 0, 0, 1880, 1881, 5, 83, 0, 0, 1881, 1882, 5, 80, 0, 0, 1882, 1883, 5, 65, 0, 0, 1883, 1884, 5, 67, 0, 0, 1884, 1885, 5, 69, 0, 0, 1885, 332, 1, 0, 0, 0, 1886, 1887, 5, 78, 0, 0, 1887, 1888, 5, 65, 0, 0, 1888, 1889, 5, 77, 0, 0, 1889, 1890, 5, 69, 0, 0, 1890, 1891, 5, 83, 0, 0, 1891, 1892, 5, 80, 0, 0, 1892, 1893, 5, 65, 0, 0, 1893, 1894, 5, 67, 0, 0, 1894, 1895, 5, 69, 0, 0, 1895, 1896, 5, 83, 0, 0, 1896, 334, 1, 0, 0, 0, 1897, 1898, 5, 78, 0, 0, 1898, 1899, 5, 65, 0, 0, 1899, 1900, 5, 78, 0, 0, 1900, 1901, 5, 79, 0, 0, 1901, 1902, 5, 83, 0, 0, 1902, 1903, 5, 69, 0, 0, 1903, 1904, 5, 67, 0, 0, 1904, 1905, 5, 79, 0, 0, 1905, 1906, 5, 78, 0, 0, 1906, 1907, 5, 68, 0, 0, 1907, 336, 1, 0, 0, 0, 1908, 1909, 5, 78, 0, 0, 1909, 1910, 5, 65, 0, 0, 1910, 1911, 5, 78, 0, 0, 1911, 1912, 5, 79, 0, 0, 1912, 1913, 5, 83, 0, 0, 1913, 1914, 5, 69, 0, 0, 1914, 1915, 5, 67, 0, 0, 1915, 1916, 5, 79, 0, 0, 1916, 1917, 5, 78, 0, 0, 1917, 1918, 5, 68, 0, 0, 1918, 1919, 5, 83, 0, 0, 1919, 338, 1, 0, 0, 0, 1920, 1921, 5, 78, 0, 0, 1921, 1922, 5, 65, 0, 0, 1922, 1923, 5, 84, 0, 0, 1923, 1924, 5, 85, 0, 0, 1924, 1925, 5, 82, 0, 0, 1925, 1926, 5, 65, 0, 0, 1926, 1927, 5, 76, 0, 0, 1927, 340, 1, 0, 0, 0, 1928, 1929, 5, 78, 0, 0, 1929, 1930, 5, 79, 0, 0, 1930, 342, 1, 0, 0, 0, 1931, 1932, 5, 78, 0, 0, 1932, 1933, 5, 79, 0, 0, 1933, 1936, 5, 84, 0, 0, 1934, 1936, 5, 33, 0, 0, 1935, 1931, 1, 0, 0, 0, 1935, 1934, 1, 0, 0, 0, 1936, 344, 1, 0, 0, 0, 1937, 1938, 5, 78, 0, 0, 1938, 1939, 5, 85, 0, 0, 1939, 1940, 5, 76, 0, 0, 1940, 1941, 5, 76, 0, 0, 1941, 346, 1, 0, 0, 0, 1942, 1943, 5, 78, 0, 0, 1943, 1944, 5, 85, 0, 0, 1944, 1945, 5, 76, 0, 0, 1945, 1946, 5, 76, 0, 0, 1946, 1947, 5, 83, 0, 0, 1947, 348, 1, 0, 0, 0, 1948, 1949, 5, 79, 0, 0, 1949, 1950, 5, 70, 0, 0, 1950, 350, 1, 0, 0, 0, 1951, 1952, 5, 79, 0, 0, 1952, 1953, 5, 70, 0, 0, 1953, 1954, 5, 70, 0, 0, 1954, 1955, 5, 83, 0, 0, 1955, 1956, 5, 69, 0, 0, 1956, 1957, 5, 84, 0, 0, 1957, 352, 1, 0, 0, 0, 1958, 1959, 5, 79, 0, 0, 1959, 1960, 5, 78, 0, 0, 1960, 354, 1, 0, 0, 0, 1961, 1962, 5, 79, 0, 0, 1962, 1963, 5, 78, 0, 0, 1963, 1964, 5, 76, 0, 0, 1964, 1965, 5, 89, 0, 0, 1965, 356, 1, 0, 0, 0, 1966, 1967, 5, 79, 0, 0, 1967, 1968, 5, 80, 0, 0, 1968, 1969, 5, 84, 0, 0, 1969, 1970, 5, 73, 0, 0, 1970, 1971, 5, 79, 0, 0, 1971, 1972, 5, 78, 0, 0, 1972, 358, 1, 0, 0, 0, 1973, 1974, 5, 79, 0, 0, 1974, 1975, 5, 80, 0, 0, 1975, 1976, 5, 84, 0, 0, 1976, 1977, 5, 73, 0, 0, 1977, 1978, 5, 79, 0, 0, 1978, 1979, 5, 78, 0, 0, 1979, 1980, 5, 83, 0, 0, 1980, 360, 1, 0, 0, 0, 1981, 1982, 5, 79, 0, 0, 1982, 1983, 5, 82, 0, 0, 1983, 362, 1, 0, 0, 0, 1984, 1985, 5, 79, 0, 0, 1985, 1986, 5, 82, 0, 0, 1986, 1987, 5, 68, 0, 0, 1987, 1988, 5, 69, 0, 0, 1988, 1989, 5, 82, 0, 0, 1989, 364, 1, 0, 0, 0, 1990, 1991, 5, 79, 0, 0, 1991, 1992, 5, 85, 0, 0, 1992, 1993, 5, 84, 0, 0, 1993, 366, 1, 0, 0, 0, 1994, 1995, 5, 79, 0, 0, 1995, 1996, 5, 85, 0, 0, 1996, 1997, 5, 84, 0, 0, 1997, 1998, 5, 69, 0, 0, 1998, 1999, 5, 82, 0, 0, 1999, 368, 1, 0, 0, 0, 2000, 2001, 5, 79, 0, 0, 2001, 2002, 5, 85, 0, 0, 2002, 2003, 5, 84, 0, 0, 2003, 2004, 5, 80, 0, 0, 2004, 2005, 5, 85, 0, 0, 2005, 2006, 5, 84, 0, 0, 2006, 2007, 5, 70, 0, 0, 2007, 2008, 5, 79, 0, 0, 2008, 2009, 5, 82, 0, 0, 2009, 2010, 5, 77, 0, 0, 2010, 2011, 5, 65, 0, 0, 2011, 2012, 5, 84, 0, 0, 2012, 370, 1, 0, 0, 0, 2013, 2014, 5, 79, 0, 0, 2014, 2015, 5, 86, 0, 0, 2015, 2016, 5, 69, 0, 0, 2016, 2017, 5, 82, 0, 0, 2017, 372, 1, 0, 0, 0, 2018, 2019, 5, 79, 0, 0, 2019, 2020, 5, 86, 0, 0, 2020, 2021, 5, 69, 0, 0, 2021, 2022, 5, 82, 0, 0, 2022, 2023, 5, 76, 0, 0, 2023, 2024, 5, 65, 0, 0, 2024, 2025, 5, 80, 0, 0, 2025, 2026, 5, 83, 0, 0, 2026, 374, 1, 0, 0, 0, 2027, 2028, 5, 79, 0, 0, 2028, 2029, 5, 86, 0, 0, 2029, 2030, 5, 69, 0, 0, 2030, 2031, 5, 82, 0, 0, 2031, 2032, 5, 76, 0, 0, 2032, 2033, 5, 65, 0, 0, 2033, 2034, 5, 89, 0, 0, 2034, 376, 1, 0, 0, 0, 2035, 2036, 5, 79, 0, 0, 2036, 2037, 5, 86, 0, 0, 2037, 2038, 5, 69, 0, 0, 2038, 2039, 5, 82, 0, 0, 2039, 2040, 5, 87, 0, 0, 2040, 2041, 5, 82, 0, 0, 2041, 2042, 5, 73, 0, 0, 2042, 2043, 5, 84, 0, 0, 2043, 2044, 5, 69, 0, 0, 2044, 378, 1, 0, 0, 0, 2045, 2046, 5, 80, 0, 0, 2046, 2047, 5, 65, 0, 0, 2047, 2048, 5, 82, 0, 0, 2048, 2049, 5, 84, 0, 0, 2049, 2050, 5, 73, 0, 0, 2050, 2051, 5, 84, 0, 0, 2051, 2052, 5, 73, 0, 0, 2052, 2053, 5, 79, 0, 0, 2053, 2054, 5, 78, 0, 0, 2054, 380, 1, 0, 0, 0, 2055, 2056, 5, 80, 0, 0, 2056, 2057, 5, 65, 0, 0, 2057, 2058, 5, 82, 0, 0, 2058, 2059, 5, 84, 0, 0, 2059, 2060, 5, 73, 0, 0, 2060, 2061, 5, 84, 0, 0, 2061, 2062, 5, 73, 0, 0, 2062, 2063, 5, 79, 0, 0, 2063, 2064, 5, 78, 0, 0, 2064, 2065, 5, 69, 0, 0, 2065, 2066, 5, 68, 0, 0, 2066, 382, 1, 0, 0, 0, 2067, 2068, 5, 80, 0, 0, 2068, 2069, 5, 65, 0, 0, 2069, 2070, 5, 82, 0, 0, 2070, 2071, 5, 84, 0, 0, 2071, 2072, 5, 73, 0, 0, 2072, 2073, 5, 84, 0, 0, 2073, 2074, 5, 73, 0, 0, 2074, 2075, 5, 79, 0, 0, 2075, 2076, 5, 78, 0, 0, 2076, 2077, 5, 83, 0, 0, 2077, 384, 1, 0, 0, 0, 2078, 2079, 5, 80, 0, 0, 2079, 2080, 5, 69, 0, 0, 2080, 2081, 5, 82, 0, 0, 2081, 2082, 5, 67, 0, 0, 2082, 2083, 5, 69, 0, 0, 2083, 2084, 5, 78, 0, 0, 2084, 2085, 5, 84, 0, 0, 2085, 2086, 5, 73, 0, 0, 2086, 2087, 5, 76, 0, 0, 2087, 2088, 5, 69, 0, 0, 2088, 2089, 5, 95, 0, 0, 2089, 2090, 5, 67, 0, 0, 2090, 2091, 5, 79, 0, 0, 2091, 2092, 5, 78, 0, 0, 2092, 2093, 5, 84, 0, 0, 2093, 386, 1, 0, 0, 0, 2094, 2095, 5, 80, 0, 0, 2095, 2096, 5, 69, 0, 0, 2096, 2097, 5, 82, 0, 0, 2097, 2098, 5, 67, 0, 0, 2098, 2099, 5, 69, 0, 0, 2099, 2100, 5, 78, 0, 0, 2100, 2101, 5, 84, 0, 0, 2101, 2102, 5, 73, 0, 0, 2102, 2103, 5, 76, 0, 0, 2103, 2104, 5, 69, 0, 0, 2104, 2105, 5, 95, 0, 0, 2105, 2106, 5, 68, 0, 0, 2106, 2107, 5, 73, 0, 0, 2107, 2108, 5, 83, 0, 0, 2108, 2109, 5, 67, 0, 0, 2109, 388, 1, 0, 0, 0, 2110, 2111, 5, 80, 0, 0, 2111, 2112, 5, 69, 0, 0, 2112, 2113, 5, 82, 0, 0, 2113, 2114, 5, 67, 0, 0, 2114, 2115, 5, 69, 0, 0, 2115, 2116, 5, 78, 0, 0, 2116, 2117, 5, 84, 0, 0, 2117, 390, 1, 0, 0, 0, 2118, 2119, 5, 80, 0, 0, 2119, 2120, 5, 73, 0, 0, 2120, 2121, 5, 86, 0, 0, 2121, 2122, 5, 79, 0, 0, 2122, 2123, 5, 84, 0, 0, 2123, 392, 1, 0, 0, 0, 2124, 2125, 5, 80, 0, 0, 2125, 2126, 5, 76, 0, 0, 2126, 2127, 5, 65, 0, 0, 2127, 2128, 5, 67, 0, 0, 2128, 2129, 5, 73, 0, 0, 2129, 2130, 5, 78, 0, 0, 2130, 2131, 5, 71, 0, 0, 2131, 394, 1, 0, 0, 0, 2132, 2133, 5, 80, 0, 0, 2133, 2134, 5, 79, 0, 0, 2134, 2135, 5, 83, 0, 0, 2135, 2136, 5, 73, 0, 0, 2136, 2137, 5, 84, 0, 0, 2137, 2138, 5, 73, 0, 0, 2138, 2139, 5, 79, 0, 0, 2139, 2140, 5, 78, 0, 0, 2140, 396, 1, 0, 0, 0, 2141, 2142, 5, 80, 0, 0, 2142, 2143, 5, 82, 0, 0, 2143, 2144, 5, 69, 0, 0, 2144, 2145, 5, 67, 0, 0, 2145, 2146, 5, 69, 0, 0, 2146, 2147, 5, 68, 0, 0, 2147, 2148, 5, 73, 0, 0, 2148, 2149, 5, 78, 0, 0, 2149, 2150, 5, 71, 0, 0, 2150, 398, 1, 0, 0, 0, 2151, 2152, 5, 80, 0, 0, 2152, 2153, 5, 82, 0, 0, 2153, 2154, 5, 73, 0, 0, 2154, 2155, 5, 77, 0, 0, 2155, 2156, 5, 65, 0, 0, 2156, 2157, 5, 82, 0, 0, 2157, 2158, 5, 89, 0, 0, 2158, 400, 1, 0, 0, 0, 2159, 2160, 5, 80, 0, 0, 2160, 2161, 5, 82, 0, 0, 2161, 2162, 5, 73, 0, 0, 2162, 2163, 5, 78, 0, 0, 2163, 2164, 5, 67, 0, 0, 2164, 2165, 5, 73, 0, 0, 2165, 2166, 5, 80, 0, 0, 2166, 2167, 5, 65, 0, 0, 2167, 2168, 5, 76, 0, 0, 2168, 2169, 5, 83, 0, 0, 2169, 402, 1, 0, 0, 0, 2170, 2171, 5, 80, 0, 0, 2171, 2172, 5, 82, 0, 0, 2172, 2173, 5, 79, 0, 0, 2173, 2174, 5, 80, 0, 0, 2174, 2175, 5, 69, 0, 0, 2175, 2176, 5, 82, 0, 0, 2176, 2177, 5, 84, 0, 0, 2177, 2178, 5, 73, 0, 0, 2178, 2179, 5, 69, 0, 0, 2179, 2180, 5, 83, 0, 0, 2180, 404, 1, 0, 0, 0, 2181, 2182, 5, 80, 0, 0, 2182, 2183, 5, 85, 0, 0, 2183, 2184, 5, 82, 0, 0, 2184, 2185, 5, 71, 0, 0, 2185, 2186, 5, 69, 0, 0, 2186, 406, 1, 0, 0, 0, 2187, 2188, 5, 81, 0, 0, 2188, 2189, 5, 85, 0, 0, 2189, 2190, 5, 65, 0, 0, 2190, 2191, 5, 82, 0, 0, 2191, 2192, 5, 84, 0, 0, 2192, 2193, 5, 69, 0, 0, 2193, 2194, 5, 82, 0, 0, 2194, 408, 1, 0, 0, 0, 2195, 2196, 5, 81, 0, 0, 2196, 2197, 5, 85, 0, 0, 2197, 2198, 5, 69, 0, 0, 2198, 2199, 5, 82, 0, 0, 2199, 2200, 5, 89, 0, 0, 2200, 410, 1, 0, 0, 0, 2201, 2202, 5, 82, 0, 0, 2202, 2203, 5, 65, 0, 0, 2203, 2204, 5, 78, 0, 0, 2204, 2205, 5, 71, 0, 0, 2205, 2206, 5, 69, 0, 0, 2206, 412, 1, 0, 0, 0, 2207, 2208, 5, 82, 0, 0, 2208, 2209, 5, 69, 0, 0, 2209, 2210, 5, 67, 0, 0, 2210, 2211, 5, 79, 0, 0, 2211, 2212, 5, 82, 0, 0, 2212, 2213, 5, 68, 0, 0, 2213, 2214, 5, 82, 0, 0, 2214, 2215, 5, 69, 0, 0, 2215, 2216, 5, 65, 0, 0, 2216, 2217, 5, 68, 0, 0, 2217, 2218, 5, 69, 0, 0, 2218, 2219, 5, 82, 0, 0, 2219, 414, 1, 0, 0, 0, 2220, 2221, 5, 82, 0, 0, 2221, 2222, 5, 69, 0, 0, 2222, 2223, 5, 67, 0, 0, 2223, 2224, 5, 79, 0, 0, 2224, 2225, 5, 82, 0, 0, 2225, 2226, 5, 68, 0, 0, 2226, 2227, 5, 87, 0, 0, 2227, 2228, 5, 82, 0, 0, 2228, 2229, 5, 73, 0, 0, 2229, 2230, 5, 84, 0, 0, 2230, 2231, 5, 69, 0, 0, 2231, 2232, 5, 82, 0, 0, 2232, 416, 1, 0, 0, 0, 2233, 2234, 5, 82, 0, 0, 2234, 2235, 5, 69, 0, 0, 2235, 2236, 5, 67, 0, 0, 2236, 2237, 5, 79, 0, 0, 2237, 2238, 5, 86, 0, 0, 2238, 2239, 5, 69, 0, 0, 2239, 2240, 5, 82, 0, 0, 2240, 418, 1, 0, 0, 0, 2241, 2242, 5, 82, 0, 0, 2242, 2243, 5, 69, 0, 0, 2243, 2244, 5, 68, 0, 0, 2244, 2245, 5, 85, 0, 0, 2245, 2246, 5, 67, 0, 0, 2246, 2247, 5, 69, 0, 0, 2247, 420, 1, 0, 0, 0, 2248, 2249, 5, 82, 0, 0, 2249, 2250, 5, 69, 0, 0, 2250, 2251, 5, 70, 0, 0, 2251, 2252, 5, 69, 0, 0, 2252, 2253, 5, 82, 0, 0, 2253, 2254, 5, 69, 0, 0, 2254, 2255, 5, 78, 0, 0, 2255, 2256, 5, 67, 0, 0, 2256, 2257, 5, 69, 0, 0, 2257, 2258, 5, 83, 0, 0, 2258, 422, 1, 0, 0, 0, 2259, 2260, 5, 82, 0, 0, 2260, 2261, 5, 69, 0, 0, 2261, 2262, 5, 70, 0, 0, 2262, 2263, 5, 82, 0, 0, 2263, 2264, 5, 69, 0, 0, 2264, 2265, 5, 83, 0, 0, 2265, 2266, 5, 72, 0, 0, 2266, 424, 1, 0, 0, 0, 2267, 2268, 5, 82, 0, 0, 2268, 2269, 5, 69, 0, 0, 2269, 2270, 5, 78, 0, 0, 2270, 2271, 5, 65, 0, 0, 2271, 2272, 5, 77, 0, 0, 2272, 2273, 5, 69, 0, 0, 2273, 426, 1, 0, 0, 0, 2274, 2275, 5, 82, 0, 0, 2275, 2276, 5, 69, 0, 0, 2276, 2277, 5, 80, 0, 0, 2277, 2278, 5, 65, 0, 0, 2278, 2279, 5, 73, 0, 0, 2279, 2280, 5, 82, 0, 0, 2280, 428, 1, 0, 0, 0, 2281, 2282, 5, 82, 0, 0, 2282, 2283, 5, 69, 0, 0, 2283, 2284, 5, 80, 0, 0, 2284, 2285, 5, 69, 0, 0, 2285, 2286, 5, 65, 0, 0, 2286, 2287, 5, 84, 0, 0, 2287, 2288, 5, 65, 0, 0, 2288, 2289, 5, 66, 0, 0, 2289, 2290, 5, 76, 0, 0, 2290, 2291, 5, 69, 0, 0, 2291, 430, 1, 0, 0, 0, 2292, 2293, 5, 82, 0, 0, 2293, 2294, 5, 69, 0, 0, 2294, 2295, 5, 80, 0, 0, 2295, 2296, 5, 76, 0, 0, 2296, 2297, 5, 65, 0, 0, 2297, 2298, 5, 67, 0, 0, 2298, 2299, 5, 69, 0, 0, 2299, 432, 1, 0, 0, 0, 2300, 2301, 5, 82, 0, 0, 2301, 2302, 5, 69, 0, 0, 2302, 2303, 5, 83, 0, 0, 2303, 2304, 5, 69, 0, 0, 2304, 2305, 5, 84, 0, 0, 2305, 434, 1, 0, 0, 0, 2306, 2307, 5, 82, 0, 0, 2307, 2308, 5, 69, 0, 0, 2308, 2309, 5, 83, 0, 0, 2309, 2310, 5, 80, 0, 0, 2310, 2311, 5, 69, 0, 0, 2311, 2312, 5, 67, 0, 0, 2312, 2313, 5, 84, 0, 0, 2313, 436, 1, 0, 0, 0, 2314, 2315, 5, 82, 0, 0, 2315, 2316, 5, 69, 0, 0, 2316, 2317, 5, 83, 0, 0, 2317, 2318, 5, 84, 0, 0, 2318, 2319, 5, 82, 0, 0, 2319, 2320, 5, 73, 0, 0, 2320, 2321, 5, 67, 0, 0, 2321, 2322, 5, 84, 0, 0, 2322, 438, 1, 0, 0, 0, 2323, 2324, 5, 82, 0, 0, 2324, 2325, 5, 69, 0, 0, 2325, 2326, 5, 86, 0, 0, 2326, 2327, 5, 79, 0, 0, 2327, 2328, 5, 75, 0, 0, 2328, 2329, 5, 69, 0, 0, 2329, 440, 1, 0, 0, 0, 2330, 2331, 5, 82, 0, 0, 2331, 2332, 5, 73, 0, 0, 2332, 2333, 5, 71, 0, 0, 2333, 2334, 5, 72, 0, 0, 2334, 2335, 5, 84, 0, 0, 2335, 442, 1, 0, 0, 0, 2336, 2337, 5, 82, 0, 0, 2337, 2338, 5, 76, 0, 0, 2338, 2339, 5, 73, 0, 0, 2339, 2340, 5, 75, 0, 0, 2340, 2348, 5, 69, 0, 0, 2341, 2342, 5, 82, 0, 0, 2342, 2343, 5, 69, 0, 0, 2343, 2344, 5, 71, 0, 0, 2344, 2345, 5, 69, 0, 0, 2345, 2346, 5, 88, 0, 0, 2346, 2348, 5, 80, 0, 0, 2347, 2336, 1, 0, 0, 0, 2347, 2341, 1, 0, 0, 0, 2348, 444, 1, 0, 0, 0, 2349, 2350, 5, 82, 0, 0, 2350, 2351, 5, 79, 0, 0, 2351, 2352, 5, 76, 0, 0, 2352, 2353, 5, 69, 0, 0, 2353, 446, 1, 0, 0, 0, 2354, 2355, 5, 82, 0, 0, 2355, 2356, 5, 79, 0, 0, 2356, 2357, 5, 76, 0, 0, 2357, 2358, 5, 69, 0, 0, 2358, 2359, 5, 83, 0, 0, 2359, 448, 1, 0, 0, 0, 2360, 2361, 5, 82, 0, 0, 2361, 2362, 5, 79, 0, 0, 2362, 2363, 5, 76, 0, 0, 2363, 2364, 5, 76, 0, 0, 2364, 2365, 5, 66, 0, 0, 2365, 2366, 5, 65, 0, 0, 2366, 2367, 5, 67, 0, 0, 2367, 2368, 5, 75, 0, 0, 2368, 450, 1, 0, 0, 0, 2369, 2370, 5, 82, 0, 0, 2370, 2371, 5, 79, 0, 0, 2371, 2372, 5, 76, 0, 0, 2372, 2373, 5, 76, 0, 0, 2373, 2374, 5, 85, 0, 0, 2374, 2375, 5, 80, 0, 0, 2375, 452, 1, 0, 0, 0, 2376, 2377, 5, 82, 0, 0, 2377, 2378, 5, 79, 0, 0, 2378, 2379, 5, 87, 0, 0, 2379, 454, 1, 0, 0, 0, 2380, 2381, 5, 82, 0, 0, 2381, 2382, 5, 79, 0, 0, 2382, 2383, 5, 87, 0, 0, 2383, 2384, 5, 83, 0, 0, 2384, 456, 1, 0, 0, 0, 2385, 2386, 5, 83, 0, 0, 2386, 2387, 5, 69, 0, 0, 2387, 2388, 5, 67, 0, 0, 2388, 2389, 5, 79, 0, 0, 2389, 2390, 5, 78, 0, 0, 2390, 2391, 5, 68, 0, 0, 2391, 458, 1, 0, 0, 0, 2392, 2393, 5, 83, 0, 0, 2393, 2394, 5, 69, 0, 0, 2394, 2395, 5, 67, 0, 0, 2395, 2396, 5, 79, 0, 0, 2396, 2397, 5, 78, 0, 0, 2397, 2398, 5, 68, 0, 0, 2398, 2399, 5, 83, 0, 0, 2399, 460, 1, 0, 0, 0, 2400, 2401, 5, 83, 0, 0, 2401, 2402, 5, 67, 0, 0, 2402, 2403, 5, 72, 0, 0, 2403, 2404, 5, 69, 0, 0, 2404, 2405, 5, 77, 0, 0, 2405, 2406, 5, 65, 0, 0, 2406, 462, 1, 0, 0, 0, 2407, 2408, 5, 83, 0, 0, 2408, 2409, 5, 67, 0, 0, 2409, 2410, 5, 72, 0, 0, 2410, 2411, 5, 69, 0, 0, 2411, 2412, 5, 77, 0, 0, 2412, 2413, 5, 65, 0, 0, 2413, 2414, 5, 83, 0, 0, 2414, 464, 1, 0, 0, 0, 2415, 2416, 5, 83, 0, 0, 2416, 2417, 5, 69, 0, 0, 2417, 2418, 5, 76, 0, 0, 2418, 2419, 5, 69, 0, 0, 2419, 2420, 5, 67, 0, 0, 2420, 2421, 5, 84, 0, 0, 2421, 466, 1, 0, 0, 0, 2422, 2423, 5, 83, 0, 0, 2423, 2424, 5, 69, 0, 0, 2424, 2425, 5, 77, 0, 0, 2425, 2426, 5, 73, 0, 0, 2426, 468, 1, 0, 0, 0, 2427, 2428, 5, 83, 0, 0, 2428, 2429, 5, 69, 0, 0, 2429, 2430, 5, 80, 0, 0, 2430, 2431, 5, 65, 0, 0, 2431, 2432, 5, 82, 0, 0, 2432, 2433, 5, 65, 0, 0, 2433, 2434, 5, 84, 0, 0, 2434, 2435, 5, 69, 0, 0, 2435, 2436, 5, 68, 0, 0, 2436, 470, 1, 0, 0, 0, 2437, 2438, 5, 83, 0, 0, 2438, 2439, 5, 69, 0, 0, 2439, 2440, 5, 82, 0, 0, 2440, 2441, 5, 68, 0, 0, 2441, 2442, 5, 69, 0, 0, 2442, 472, 1, 0, 0, 0, 2443, 2444, 5, 83, 0, 0, 2444, 2445, 5, 69, 0, 0, 2445, 2446, 5, 82, 0, 0, 2446, 2447, 5, 68, 0, 0, 2447, 2448, 5, 69, 0, 0, 2448, 2449, 5, 80, 0, 0, 2449, 2450, 5, 82, 0, 0, 2450, 2451, 5, 79, 0, 0, 2451, 2452, 5, 80, 0, 0, 2452, 2453, 5, 69, 0, 0, 2453, 2454, 5, 82, 0, 0, 2454, 2455, 5, 84, 0, 0, 2455, 2456, 5, 73, 0, 0, 2456, 2457, 5, 69, 0, 0, 2457, 2458, 5, 83, 0, 0, 2458, 474, 1, 0, 0, 0, 2459, 2460, 5, 83, 0, 0, 2460, 2461, 5, 69, 0, 0, 2461, 2462, 5, 83, 0, 0, 2462, 2463, 5, 83, 0, 0, 2463, 2464, 5, 73, 0, 0, 2464, 2465, 5, 79, 0, 0, 2465, 2466, 5, 78, 0, 0, 2466, 2467, 5, 95, 0, 0, 2467, 2468, 5, 85, 0, 0, 2468, 2469, 5, 83, 0, 0, 2469, 2470, 5, 69, 0, 0, 2470, 2471, 5, 82, 0, 0, 2471, 476, 1, 0, 0, 0, 2472, 2473, 5, 83, 0, 0, 2473, 2474, 5, 69, 0, 0, 2474, 2475, 5, 84, 0, 0, 2475, 478, 1, 0, 0, 0, 2476, 2477, 5, 77, 0, 0, 2477, 2478, 5, 73, 0, 0, 2478, 2479, 5, 78, 0, 0, 2479, 2480, 5, 85, 0, 0, 2480, 2481, 5, 83, 0, 0, 2481, 480, 1, 0, 0, 0, 2482, 2483, 5, 83, 0, 0, 2483, 2484, 5, 69, 0, 0, 2484, 2485, 5, 84, 0, 0, 2485, 2486, 5, 83, 0, 0, 2486, 482, 1, 0, 0, 0, 2487, 2488, 5, 83, 0, 0, 2488, 2489, 5, 72, 0, 0, 2489, 2490, 5, 79, 0, 0, 2490, 2491, 5, 87, 0, 0, 2491, 484, 1, 0, 0, 0, 2492, 2493, 5, 83, 0, 0, 2493, 2494, 5, 75, 0, 0, 2494, 2495, 5, 69, 0, 0, 2495, 2496, 5, 87, 0, 0, 2496, 2497, 5, 69, 0, 0, 2497, 2498, 5, 68, 0, 0, 2498, 486, 1, 0, 0, 0, 2499, 2500, 5, 83, 0, 0, 2500, 2501, 5, 79, 0, 0, 2501, 2502, 5, 77, 0, 0, 2502, 2503, 5, 69, 0, 0, 2503, 488, 1, 0, 0, 0, 2504, 2505, 5, 83, 0, 0, 2505, 2506, 5, 79, 0, 0, 2506, 2507, 5, 82, 0, 0, 2507, 2508, 5, 84, 0, 0, 2508, 490, 1, 0, 0, 0, 2509, 2510, 5, 83, 0, 0, 2510, 2511, 5, 79, 0, 0, 2511, 2512, 5, 82, 0, 0, 2512, 2513, 5, 84, 0, 0, 2513, 2514, 5, 69, 0, 0, 2514, 2515, 5, 68, 0, 0, 2515, 492, 1, 0, 0, 0, 2516, 2517, 5, 83, 0, 0, 2517, 2518, 5, 79, 0, 0, 2518, 2519, 5, 85, 0, 0, 2519, 2520, 5, 82, 0, 0, 2520, 2521, 5, 67, 0, 0, 2521, 2522, 5, 69, 0, 0, 2522, 494, 1, 0, 0, 0, 2523, 2524, 5, 83, 0, 0, 2524, 2525, 5, 84, 0, 0, 2525, 2526, 5, 65, 0, 0, 2526, 2527, 5, 82, 0, 0, 2527, 2528, 5, 84, 0, 0, 2528, 496, 1, 0, 0, 0, 2529, 2530, 5, 83, 0, 0, 2530, 2531, 5, 84, 0, 0, 2531, 2532, 5, 65, 0, 0, 2532, 2533, 5, 84, 0, 0, 2533, 2534, 5, 73, 0, 0, 2534, 2535, 5, 83, 0, 0, 2535, 2536, 5, 84, 0, 0, 2536, 2537, 5, 73, 0, 0, 2537, 2538, 5, 67, 0, 0, 2538, 2539, 5, 83, 0, 0, 2539, 498, 1, 0, 0, 0, 2540, 2541, 5, 83, 0, 0, 2541, 2542, 5, 84, 0, 0, 2542, 2543, 5, 79, 0, 0, 2543, 2544, 5, 82, 0, 0, 2544, 2545, 5, 69, 0, 0, 2545, 2546, 5, 68, 0, 0, 2546, 500, 1, 0, 0, 0, 2547, 2548, 5, 83, 0, 0, 2548, 2549, 5, 84, 0, 0, 2549, 2550, 5, 82, 0, 0, 2550, 2551, 5, 65, 0, 0, 2551, 2552, 5, 84, 0, 0, 2552, 2553, 5, 73, 0, 0, 2553, 2554, 5, 70, 0, 0, 2554, 2555, 5, 89, 0, 0, 2555, 502, 1, 0, 0, 0, 2556, 2557, 5, 83, 0, 0, 2557, 2558, 5, 84, 0, 0, 2558, 2559, 5, 82, 0, 0, 2559, 2560, 5, 85, 0, 0, 2560, 2561, 5, 67, 0, 0, 2561, 2562, 5, 84, 0, 0, 2562, 504, 1, 0, 0, 0, 2563, 2564, 5, 83, 0, 0, 2564, 2565, 5, 85, 0, 0, 2565, 2566, 5, 66, 0, 0, 2566, 2567, 5, 83, 0, 0, 2567, 2568, 5, 84, 0, 0, 2568, 2569, 5, 82, 0, 0, 2569, 506, 1, 0, 0, 0, 2570, 2571, 5, 83, 0, 0, 2571, 2572, 5, 85, 0, 0, 2572, 2573, 5, 66, 0, 0, 2573, 2574, 5, 83, 0, 0, 2574, 2575, 5, 84, 0, 0, 2575, 2576, 5, 82, 0, 0, 2576, 2577, 5, 73, 0, 0, 2577, 2578, 5, 78, 0, 0, 2578, 2579, 5, 71, 0, 0, 2579, 508, 1, 0, 0, 0, 2580, 2581, 5, 83, 0, 0, 2581, 2582, 5, 89, 0, 0, 2582, 2583, 5, 78, 0, 0, 2583, 2584, 5, 67, 0, 0, 2584, 510, 1, 0, 0, 0, 2585, 2586, 5, 83, 0, 0, 2586, 2587, 5, 89, 0, 0, 2587, 2588, 5, 83, 0, 0, 2588, 2589, 5, 84, 0, 0, 2589, 2590, 5, 69, 0, 0, 2590, 2591, 5, 77, 0, 0, 2591, 2592, 5, 95, 0, 0, 2592, 2593, 5, 84, 0, 0, 2593, 2594, 5, 73, 0, 0, 2594, 2595, 5, 77, 0, 0, 2595, 2596, 5, 69, 0, 0, 2596, 512, 1, 0, 0, 0, 2597, 2598, 5, 83, 0, 0, 2598, 2599, 5, 89, 0, 0, 2599, 2600, 5, 83, 0, 0, 2600, 2601, 5, 84, 0, 0, 2601, 2602, 5, 69, 0, 0, 2602, 2603, 5, 77, 0, 0, 2603, 2604, 5, 95, 0, 0, 2604, 2605, 5, 86, 0, 0, 2605, 2606, 5, 69, 0, 0, 2606, 2607, 5, 82, 0, 0, 2607, 2608, 5, 83, 0, 0, 2608, 2609, 5, 73, 0, 0, 2609, 2610, 5, 79, 0, 0, 2610, 2611, 5, 78, 0, 0, 2611, 514, 1, 0, 0, 0, 2612, 2613, 5, 84, 0, 0, 2613, 2614, 5, 65, 0, 0, 2614, 2615, 5, 66, 0, 0, 2615, 2616, 5, 76, 0, 0, 2616, 2617, 5, 69, 0, 0, 2617, 516, 1, 0, 0, 0, 2618, 2619, 5, 84, 0, 0, 2619, 2620, 5, 65, 0, 0, 2620, 2621, 5, 66, 0, 0, 2621, 2622, 5, 76, 0, 0, 2622, 2623, 5, 69, 0, 0, 2623, 2624, 5, 83, 0, 0, 2624, 518, 1, 0, 0, 0, 2625, 2626, 5, 84, 0, 0, 2626, 2627, 5, 65, 0, 0, 2627, 2628, 5, 66, 0, 0, 2628, 2629, 5, 76, 0, 0, 2629, 2630, 5, 69, 0, 0, 2630, 2631, 5, 83, 0, 0, 2631, 2632, 5, 65, 0, 0, 2632, 2633, 5, 77, 0, 0, 2633, 2634, 5, 80, 0, 0, 2634, 2635, 5, 76, 0, 0, 2635, 2636, 5, 69, 0, 0, 2636, 520, 1, 0, 0, 0, 2637, 2638, 5, 84, 0, 0, 2638, 2639, 5, 65, 0, 0, 2639, 2640, 5, 82, 0, 0, 2640, 2641, 5, 71, 0, 0, 2641, 2642, 5, 69, 0, 0, 2642, 2643, 5, 84, 0, 0, 2643, 522, 1, 0, 0, 0, 2644, 2645, 5, 84, 0, 0, 2645, 2646, 5, 66, 0, 0, 2646, 2647, 5, 76, 0, 0, 2647, 2648, 5, 80, 0, 0, 2648, 2649, 5, 82, 0, 0, 2649, 2650, 5, 79, 0, 0, 2650, 2651, 5, 80, 0, 0, 2651, 2652, 5, 69, 0, 0, 2652, 2653, 5, 82, 0, 0, 2653, 2654, 5, 84, 0, 0, 2654, 2655, 5, 73, 0, 0, 2655, 2656, 5, 69, 0, 0, 2656, 2657, 5, 83, 0, 0, 2657, 524, 1, 0, 0, 0, 2658, 2659, 5, 84, 0, 0, 2659, 2660, 5, 69, 0, 0, 2660, 2661, 5, 77, 0, 0, 2661, 2662, 5, 80, 0, 0, 2662, 2663, 5, 79, 0, 0, 2663, 2664, 5, 82, 0, 0, 2664, 2665, 5, 65, 0, 0, 2665, 2666, 5, 82, 0, 0, 2666, 2672, 5, 89, 0, 0, 2667, 2668, 5, 84, 0, 0, 2668, 2669, 5, 69, 0, 0, 2669, 2670, 5, 77, 0, 0, 2670, 2672, 5, 80, 0, 0, 2671, 2658, 1, 0, 0, 0, 2671, 2667, 1, 0, 0, 0, 2672, 526, 1, 0, 0, 0, 2673, 2674, 5, 84, 0, 0, 2674, 2675, 5, 69, 0, 0, 2675, 2676, 5, 82, 0, 0, 2676, 2677, 5, 77, 0, 0, 2677, 2678, 5, 73, 0, 0, 2678, 2679, 5, 78, 0, 0, 2679, 2680, 5, 65, 0, 0, 2680, 2681, 5, 84, 0, 0, 2681, 2682, 5, 69, 0, 0, 2682, 2683, 5, 68, 0, 0, 2683, 528, 1, 0, 0, 0, 2684, 2685, 5, 84, 0, 0, 2685, 2686, 5, 72, 0, 0, 2686, 2687, 5, 69, 0, 0, 2687, 2688, 5, 78, 0, 0, 2688, 530, 1, 0, 0, 0, 2689, 2690, 5, 84, 0, 0, 2690, 2691, 5, 73, 0, 0, 2691, 2692, 5, 77, 0, 0, 2692, 2693, 5, 69, 0, 0, 2693, 532, 1, 0, 0, 0, 2694, 2695, 5, 84, 0, 0, 2695, 2696, 5, 73, 0, 0, 2696, 2697, 5, 77, 0, 0, 2697, 2698, 5, 69, 0, 0, 2698, 2699, 5, 83, 0, 0, 2699, 2700, 5, 84, 0, 0, 2700, 2701, 5, 65, 0, 0, 2701, 2702, 5, 77, 0, 0, 2702, 2703, 5, 80, 0, 0, 2703, 534, 1, 0, 0, 0, 2704, 2705, 5, 84, 0, 0, 2705, 2706, 5, 73, 0, 0, 2706, 2707, 5, 77, 0, 0, 2707, 2708, 5, 69, 0, 0, 2708, 2709, 5, 83, 0, 0, 2709, 2710, 5, 84, 0, 0, 2710, 2711, 5, 65, 0, 0, 2711, 2712, 5, 77, 0, 0, 2712, 2713, 5, 80, 0, 0, 2713, 2714, 5, 65, 0, 0, 2714, 2715, 5, 68, 0, 0, 2715, 2716, 5, 68, 0, 0, 2716, 536, 1, 0, 0, 0, 2717, 2718, 5, 84, 0, 0, 2718, 2719, 5, 73, 0, 0, 2719, 2720, 5, 77, 0, 0, 2720, 2721, 5, 69, 0, 0, 2721, 2722, 5, 83, 0, 0, 2722, 2723, 5, 84, 0, 0, 2723, 2724, 5, 65, 0, 0, 2724, 2725, 5, 77, 0, 0, 2725, 2726, 5, 80, 0, 0, 2726, 2727, 5, 68, 0, 0, 2727, 2728, 5, 73, 0, 0, 2728, 2729, 5, 70, 0, 0, 2729, 2730, 5, 70, 0, 0, 2730, 538, 1, 0, 0, 0, 2731, 2732, 5, 84, 0, 0, 2732, 2733, 5, 79, 0, 0, 2733, 540, 1, 0, 0, 0, 2734, 2735, 5, 84, 0, 0, 2735, 2736, 5, 79, 0, 0, 2736, 2737, 5, 85, 0, 0, 2737, 2738, 5, 67, 0, 0, 2738, 2739, 5, 72, 0, 0, 2739, 542, 1, 0, 0, 0, 2740, 2741, 5, 84, 0, 0, 2741, 2742, 5, 82, 0, 0, 2742, 2743, 5, 65, 0, 0, 2743, 2744, 5, 73, 0, 0, 2744, 2745, 5, 76, 0, 0, 2745, 2746, 5, 73, 0, 0, 2746, 2747, 5, 78, 0, 0, 2747, 2748, 5, 71, 0, 0, 2748, 544, 1, 0, 0, 0, 2749, 2750, 5, 84, 0, 0, 2750, 2751, 5, 82, 0, 0, 2751, 2752, 5, 65, 0, 0, 2752, 2753, 5, 78, 0, 0, 2753, 2754, 5, 83, 0, 0, 2754, 2755, 5, 65, 0, 0, 2755, 2756, 5, 67, 0, 0, 2756, 2757, 5, 84, 0, 0, 2757, 2758, 5, 73, 0, 0, 2758, 2759, 5, 79, 0, 0, 2759, 2760, 5, 78, 0, 0, 2760, 546, 1, 0, 0, 0, 2761, 2762, 5, 84, 0, 0, 2762, 2763, 5, 82, 0, 0, 2763, 2764, 5, 65, 0, 0, 2764, 2765, 5, 78, 0, 0, 2765, 2766, 5, 83, 0, 0, 2766, 2767, 5, 65, 0, 0, 2767, 2768, 5, 67, 0, 0, 2768, 2769, 5, 84, 0, 0, 2769, 2770, 5, 73, 0, 0, 2770, 2771, 5, 79, 0, 0, 2771, 2772, 5, 78, 0, 0, 2772, 2773, 5, 83, 0, 0, 2773, 548, 1, 0, 0, 0, 2774, 2775, 5, 84, 0, 0, 2775, 2776, 5, 82, 0, 0, 2776, 2777, 5, 65, 0, 0, 2777, 2778, 5, 78, 0, 0, 2778, 2779, 5, 83, 0, 0, 2779, 2780, 5, 70, 0, 0, 2780, 2781, 5, 79, 0, 0, 2781, 2782, 5, 82, 0, 0, 2782, 2783, 5, 77, 0, 0, 2783, 550, 1, 0, 0, 0, 2784, 2785, 5, 84, 0, 0, 2785, 2786, 5, 82, 0, 0, 2786, 2787, 5, 73, 0, 0, 2787, 2788, 5, 77, 0, 0, 2788, 552, 1, 0, 0, 0, 2789, 2790, 5, 84, 0, 0, 2790, 2791, 5, 82, 0, 0, 2791, 2792, 5, 85, 0, 0, 2792, 2793, 5, 69, 0, 0, 2793, 554, 1, 0, 0, 0, 2794, 2795, 5, 84, 0, 0, 2795, 2796, 5, 82, 0, 0, 2796, 2797, 5, 85, 0, 0, 2797, 2798, 5, 78, 0, 0, 2798, 2799, 5, 67, 0, 0, 2799, 2800, 5, 65, 0, 0, 2800, 2801, 5, 84, 0, 0, 2801, 2802, 5, 69, 0, 0, 2802, 556, 1, 0, 0, 0, 2803, 2804, 5, 84, 0, 0, 2804, 2805, 5, 82, 0, 0, 2805, 2806, 5, 89, 0, 0, 2806, 2807, 5, 95, 0, 0, 2807, 2808, 5, 67, 0, 0, 2808, 2809, 5, 65, 0, 0, 2809, 2810, 5, 83, 0, 0, 2810, 2811, 5, 84, 0, 0, 2811, 558, 1, 0, 0, 0, 2812, 2813, 5, 84, 0, 0, 2813, 2814, 5, 89, 0, 0, 2814, 2815, 5, 80, 0, 0, 2815, 2816, 5, 69, 0, 0, 2816, 560, 1, 0, 0, 0, 2817, 2818, 5, 85, 0, 0, 2818, 2819, 5, 78, 0, 0, 2819, 2820, 5, 65, 0, 0, 2820, 2821, 5, 82, 0, 0, 2821, 2822, 5, 67, 0, 0, 2822, 2823, 5, 72, 0, 0, 2823, 2824, 5, 73, 0, 0, 2824, 2825, 5, 86, 0, 0, 2825, 2826, 5, 69, 0, 0, 2826, 562, 1, 0, 0, 0, 2827, 2828, 5, 85, 0, 0, 2828, 2829, 5, 78, 0, 0, 2829, 2830, 5, 66, 0, 0, 2830, 2831, 5, 79, 0, 0, 2831, 2832, 5, 85, 0, 0, 2832, 2833, 5, 78, 0, 0, 2833, 2834, 5, 68, 0, 0, 2834, 2835, 5, 69, 0, 0, 2835, 2836, 5, 68, 0, 0, 2836, 564, 1, 0, 0, 0, 2837, 2838, 5, 85, 0, 0, 2838, 2839, 5, 78, 0, 0, 2839, 2840, 5, 67, 0, 0, 2840, 2841, 5, 65, 0, 0, 2841, 2842, 5, 67, 0, 0, 2842, 2843, 5, 72, 0, 0, 2843, 2844, 5, 69, 0, 0, 2844, 566, 1, 0, 0, 0, 2845, 2846, 5, 85, 0, 0, 2846, 2847, 5, 78, 0, 0, 2847, 2848, 5, 73, 0, 0, 2848, 2849, 5, 79, 0, 0, 2849, 2850, 5, 78, 0, 0, 2850, 568, 1, 0, 0, 0, 2851, 2852, 5, 85, 0, 0, 2852, 2853, 5, 78, 0, 0, 2853, 2854, 5, 73, 0, 0, 2854, 2855, 5, 81, 0, 0, 2855, 2856, 5, 85, 0, 0, 2856, 2857, 5, 69, 0, 0, 2857, 570, 1, 0, 0, 0, 2858, 2859, 5, 85, 0, 0, 2859, 2860, 5, 78, 0, 0, 2860, 2861, 5, 75, 0, 0, 2861, 2862, 5, 78, 0, 0, 2862, 2863, 5, 79, 0, 0, 2863, 2864, 5, 87, 0, 0, 2864, 2865, 5, 78, 0, 0, 2865, 572, 1, 0, 0, 0, 2866, 2867, 5, 85, 0, 0, 2867, 2868, 5, 78, 0, 0, 2868, 2869, 5, 76, 0, 0, 2869, 2870, 5, 79, 0, 0, 2870, 2871, 5, 67, 0, 0, 2871, 2872, 5, 75, 0, 0, 2872, 574, 1, 0, 0, 0, 2873, 2874, 5, 85, 0, 0, 2874, 2875, 5, 78, 0, 0, 2875, 2876, 5, 80, 0, 0, 2876, 2877, 5, 73, 0, 0, 2877, 2878, 5, 86, 0, 0, 2878, 2879, 5, 79, 0, 0, 2879, 2880, 5, 84, 0, 0, 2880, 576, 1, 0, 0, 0, 2881, 2882, 5, 85, 0, 0, 2882, 2883, 5, 78, 0, 0, 2883, 2884, 5, 83, 0, 0, 2884, 2885, 5, 69, 0, 0, 2885, 2886, 5, 84, 0, 0, 2886, 578, 1, 0, 0, 0, 2887, 2888, 5, 85, 0, 0, 2888, 2889, 5, 80, 0, 0, 2889, 2890, 5, 68, 0, 0, 2890, 2891, 5, 65, 0, 0, 2891, 2892, 5, 84, 0, 0, 2892, 2893, 5, 69, 0, 0, 2893, 580, 1, 0, 0, 0, 2894, 2895, 5, 85, 0, 0, 2895, 2896, 5, 83, 0, 0, 2896, 2897, 5, 69, 0, 0, 2897, 582, 1, 0, 0, 0, 2898, 2899, 5, 85, 0, 0, 2899, 2900, 5, 83, 0, 0, 2900, 2901, 5, 69, 0, 0, 2901, 2902, 5, 82, 0, 0, 2902, 584, 1, 0, 0, 0, 2903, 2904, 5, 85, 0, 0, 2904, 2905, 5, 83, 0, 0, 2905, 2906, 5, 73, 0, 0, 2906, 2907, 5, 78, 0, 0, 2907, 2908, 5, 71, 0, 0, 2908, 586, 1, 0, 0, 0, 2909, 2910, 5, 86, 0, 0, 2910, 2911, 5, 65, 0, 0, 2911, 2912, 5, 76, 0, 0, 2912, 2913, 5, 85, 0, 0, 2913, 2914, 5, 69, 0, 0, 2914, 2915, 5, 83, 0, 0, 2915, 588, 1, 0, 0, 0, 2916, 2917, 5, 86, 0, 0, 2917, 2918, 5, 69, 0, 0, 2918, 2919, 5, 82, 0, 0, 2919, 2920, 5, 83, 0, 0, 2920, 2921, 5, 73, 0, 0, 2921, 2922, 5, 79, 0, 0, 2922, 2923, 5, 78, 0, 0, 2923, 590, 1, 0, 0, 0, 2924, 2925, 5, 86, 0, 0, 2925, 2926, 5, 73, 0, 0, 2926, 2927, 5, 69, 0, 0, 2927, 2928, 5, 87, 0, 0, 2928, 592, 1, 0, 0, 0, 2929, 2930, 5, 86, 0, 0, 2930, 2931, 5, 73, 0, 0, 2931, 2932, 5, 69, 0, 0, 2932, 2933, 5, 87, 0, 0, 2933, 2934, 5, 83, 0, 0, 2934, 594, 1, 0, 0, 0, 2935, 2936, 5, 87, 0, 0, 2936, 2937, 5, 69, 0, 0, 2937, 2938, 5, 69, 0, 0, 2938, 2939, 5, 75, 0, 0, 2939, 596, 1, 0, 0, 0, 2940, 2941, 5, 87, 0, 0, 2941, 2942, 5, 69, 0, 0, 2942, 2943, 5, 69, 0, 0, 2943, 2944, 5, 75, 0, 0, 2944, 2945, 5, 83, 0, 0, 2945, 598, 1, 0, 0, 0, 2946, 2947, 5, 87, 0, 0, 2947, 2948, 5, 72, 0, 0, 2948, 2949, 5, 69, 0, 0, 2949, 2950, 5, 78, 0, 0, 2950, 600, 1, 0, 0, 0, 2951, 2952, 5, 87, 0, 0, 2952, 2953, 5, 72, 0, 0, 2953, 2954, 5, 69, 0, 0, 2954, 2955, 5, 82, 0, 0, 2955, 2956, 5, 69, 0, 0, 2956, 602, 1, 0, 0, 0, 2957, 2958, 5, 87, 0, 0, 2958, 2959, 5, 73, 0, 0, 2959, 2960, 5, 78, 0, 0, 2960, 2961, 5, 68, 0, 0, 2961, 2962, 5, 79, 0, 0, 2962, 2963, 5, 87, 0, 0, 2963, 604, 1, 0, 0, 0, 2964, 2965, 5, 87, 0, 0, 2965, 2966, 5, 73, 0, 0, 2966, 2967, 5, 84, 0, 0, 2967, 2968, 5, 72, 0, 0, 2968, 606, 1, 0, 0, 0, 2969, 2970, 5, 87, 0, 0, 2970, 2971, 5, 73, 0, 0, 2971, 2972, 5, 84, 0, 0, 2972, 2973, 5, 72, 0, 0, 2973, 2974, 5, 73, 0, 0, 2974, 2975, 5, 78, 0, 0, 2975, 608, 1, 0, 0, 0, 2976, 2977, 5, 89, 0, 0, 2977, 2978, 5, 69, 0, 0, 2978, 2979, 5, 65, 0, 0, 2979, 2980, 5, 82, 0, 0, 2980, 610, 1, 0, 0, 0, 2981, 2982, 5, 89, 0, 0, 2982, 2983, 5, 69, 0, 0, 2983, 2984, 5, 65, 0, 0, 2984, 2985, 5, 82, 0, 0, 2985, 2986, 5, 83, 0, 0, 2986, 612, 1, 0, 0, 0, 2987, 2988, 5, 90, 0, 0, 2988, 2989, 5, 79, 0, 0, 2989, 2990, 5, 78, 0, 0, 2990, 2991, 5, 69, 0, 0, 2991, 614, 1, 0, 0, 0, 2992, 2996, 5, 61, 0, 0, 2993, 2994, 5, 61, 0, 0, 2994, 2996, 5, 61, 0, 0, 2995, 2992, 1, 0, 0, 0, 2995, 2993, 1, 0, 0, 0, 2996, 616, 1, 0, 0, 0, 2997, 2998, 5, 60, 0, 0, 2998, 2999, 5, 61, 0, 0, 2999, 3000, 5, 62, 0, 0, 3000, 618, 1, 0, 0, 0, 3001, 3002, 5, 60, 0, 0, 3002, 3003, 5, 62, 0, 0, 3003, 620, 1, 0, 0, 0, 3004, 3005, 5, 33, 0, 0, 3005, 3006, 5, 61, 0, 0, 3006, 622, 1, 0, 0, 0, 3007, 3008, 5, 60, 0, 0, 3008, 624, 1, 0, 0, 0, 3009, 3010, 5, 60, 0, 0, 3010, 3014, 5, 61, 0, 0, 3011, 3012, 5, 33, 0, 0, 3012, 3014, 5, 62, 0, 0, 3013, 3009, 1, 0, 0, 0, 3013, 3011, 1, 0, 0, 0, 3014, 626, 1, 0, 0, 0, 3015, 3016, 5, 62, 0, 0, 3016, 628, 1, 0, 0, 0, 3017, 3018, 5, 62, 0, 0, 3018, 3022, 5, 61, 0, 0, 3019, 3020, 5, 33, 0, 0, 3020, 3022, 5, 60, 0, 0, 3021, 3017, 1, 0, 0, 0, 3021, 3019, 1, 0, 0, 0, 3022, 630, 1, 0, 0, 0, 3023, 3024, 5, 43, 0, 0, 3024, 632, 1, 0, 0, 0, 3025, 3026, 5, 45, 0, 0, 3026, 634, 1, 0, 0, 0, 3027, 3028, 5, 42, 0, 0, 3028, 636, 1, 0, 0, 0, 3029, 3030, 5, 47, 0, 0, 3030, 638, 1, 0, 0, 0, 3031, 3032, 5, 37, 0, 0, 3032, 640, 1, 0, 0, 0, 3033, 3034, 5, 126, 0, 0, 3034, 642, 1, 0, 0, 0, 3035, 3036, 5, 38, 0, 0, 3036, 644, 1, 0, 0, 0, 3037, 3038, 5, 124, 0, 0, 3038, 646, 1, 0, 0, 0, 3039, 3040, 5, 124, 0, 0, 3040, 3041, 5, 124, 0, 0, 3041, 648, 1, 0, 0, 0, 3042, 3043, 5, 94, 0, 0, 3043, 650, 1, 0, 0, 0, 3044, 3045, 5, 58, 0, 0, 3045, 652, 1, 0, 0, 0, 3046, 3047, 5, 45, 0, 0, 3047, 3048, 5, 62, 0, 0, 3048, 654, 1, 0, 0, 0, 3049, 3050, 5, 47, 0, 0, 3050, 3051, 5, 42, 0, 0, 3051, 3052, 5, 43, 0, 0, 3052, 656, 1, 0, 0, 0, 3053, 3054, 5, 42, 0, 0, 3054, 3055, 5, 47, 0, 0, 3055, 658, 1, 0, 0, 0, 3056, 3062, 5, 39, 0, 0, 3057, 3061, 8, 0, 0, 0, 3058, 3059, 5, 92, 0, 0, 3059, 3061, 9, 0, 0, 0, 3060, 3057, 1, 0, 0, 0, 3060, 3058, 1, 0, 0, 0, 3061, 3064, 1, 0, 0, 0, 3062, 3060, 1, 0, 0, 0, 3062, 3063, 1, 0, 0, 0, 3063, 3065, 1, 0, 0, 0, 3064, 3062, 1, 0, 0, 0, 3065, 3087, 5, 39, 0, 0, 3066, 3067, 5, 82, 0, 0, 3067, 3068, 5, 39, 0, 0, 3068, 3072, 1, 0, 0, 0, 3069, 3071, 8, 1, 0, 0, 3070, 3069, 1, 0, 0, 0, 3071, 3074, 1, 0, 0, 0, 3072, 3070, 1, 0, 0, 0, 3072, 3073, 1, 0, 0, 0, 3073, 3075, 1, 0, 0, 0, 3074, 3072, 1, 0, 0, 0, 3075, 3087, 5, 39, 0, 0, 3076, 3077, 5, 82, 0, 0, 3077, 3078, 5, 34, 0, 0, 3078, 3082, 1, 0, 0, 0, 3079, 3081, 8, 2, 0, 0, 3080, 3079, 1, 0, 0, 0, 3081, 3084, 1, 0, 0, 0, 3082, 3080, 1, 0, 0, 0, 3082, 3083, 1, 0, 0, 0, 3083, 3085, 1, 0, 0, 0, 3084, 3082, 1, 0, 0, 0, 3085, 3087, 5, 34, 0, 0, 3086, 3056, 1, 0, 0, 0, 3086, 3066, 1, 0, 0, 0, 3086, 3076, 1, 0, 0, 0, 3087, 660, 1, 0, 0, 0, 3088, 3094, 5, 34, 0, 0, 3089, 3093, 8, 3, 0, 0, 3090, 3091, 5, 92, 0, 0, 3091, 3093, 9, 0, 0, 0, 3092, 3089, 1, 0, 0, 0, 3092, 3090, 1, 0, 0, 0, 3093, 3096, 1, 0, 0, 0, 3094, 3092, 1, 0, 0, 0, 3094, 3095, 1, 0, 0, 0, 3095, 3097, 1, 0, 0, 0, 3096, 3094, 1, 0, 0, 0, 3097, 3098, 5, 34, 0, 0, 3098, 662, 1, 0, 0, 0, 3099, 3101, 3, 689, 344, 0, 3100, 3099, 1, 0, 0, 0, 3101, 3102, 1, 0, 0, 0, 3102, 3100, 1, 0, 0, 0, 3102, 3103, 1, 0, 0, 0, 3103, 3104, 1, 0, 0, 0, 3104, 3105, 5, 76, 0, 0, 3105, 664, 1, 0, 0, 0, 3106, 3108, 3, 689, 344, 0, 3107, 3106, 1, 0, 0, 0, 3108, 3109, 1, 0, 0, 0, 3109, 3107, 1, 0, 0, 0, 3109, 3110, 1, 0, 0, 0, 3110, 3111, 1, 0, 0, 0, 3111, 3112, 5, 83, 0, 0, 3112, 666, 1, 0, 0, 0, 3113, 3115, 3, 689, 344, 0, 3114, 3113, 1, 0, 0, 0, 3115, 3116, 1, 0, 0, 0, 3116, 3114, 1, 0, 0, 0, 3116, 3117, 1, 0, 0, 0, 3117, 3118, 1, 0, 0, 0, 3118, 3119, 5, 89, 0, 0, 3119, 668, 1, 0, 0, 0, 3120, 3122, 3, 689, 344, 0, 3121, 3120, 1, 0, 0, 0, 3122, 3123, 1, 0, 0, 0, 3123, 3121, 1, 0, 0, 0, 3123, 3124, 1, 0, 0, 0, 3124, 670, 1, 0, 0, 0, 3125, 3127, 3, 689, 344, 0, 3126, 3125, 1, 0, 0, 0, 3127, 3128, 1, 0, 0, 0, 3128, 3126, 1, 0, 0, 0, 3128, 3129, 1, 0, 0, 0, 3129, 3130, 1, 0, 0, 0, 3130, 3131, 3, 687, 343, 0, 3131, 3136, 1, 0, 0, 0, 3132, 3133, 3, 685, 342, 0, 3133, 3134, 3, 687, 343, 0, 3134, 3136, 1, 0, 0, 0, 3135, 3126, 1, 0, 0, 0, 3135, 3132, 1, 0, 0, 0, 3136, 672, 1, 0, 0, 0, 3137, 3138, 3, 685, 342, 0, 3138, 674, 1, 0, 0, 0, 3139, 3141, 3, 689, 344, 0, 3140, 3139, 1, 0, 0, 0, 3141, 3142, 1, 0, 0, 0, 3142, 3140, 1, 0, 0, 0, 3142, 3143, 1, 0, 0, 0, 3143, 3145, 1, 0, 0, 0, 3144, 3146, 3, 687, 343, 0, 3145, 3144, 1, 0, 0, 0, 3145, 3146, 1, 0, 0, 0, 3146, 3147, 1, 0, 0, 0, 3147, 3148, 5, 70, 0, 0, 3148, 3156, 1, 0, 0, 0, 3149, 3151, 3, 685, 342, 0, 3150, 3152, 3, 687, 343, 0, 3151, 3150, 1, 0, 0, 0, 3151, 3152, 1, 0, 0, 0, 3152, 3153, 1, 0, 0, 0, 3153, 3154, 5, 70, 0, 0, 3154, 3156, 1, 0, 0, 0, 3155, 3140, 1, 0, 0, 0, 3155, 3149, 1, 0, 0, 0, 3156, 676, 1, 0, 0, 0, 3157, 3159, 3, 689, 344, 0, 3158, 3157, 1, 0, 0, 0, 3159, 3160, 1, 0, 0, 0, 3160, 3158, 1, 0, 0, 0, 3160, 3161, 1, 0, 0, 0, 3161, 3163, 1, 0, 0, 0, 3162, 3164, 3, 687, 343, 0, 3163, 3162, 1, 0, 0, 0, 3163, 3164, 1, 0, 0, 0, 3164, 3165, 1, 0, 0, 0, 3165, 3166, 5, 68, 0, 0, 3166, 3174, 1, 0, 0, 0, 3167, 3169, 3, 685, 342, 0, 3168, 3170, 3, 687, 343, 0, 3169, 3168, 1, 0, 0, 0, 3169, 3170, 1, 0, 0, 0, 3170, 3171, 1, 0, 0, 0, 3171, 3172, 5, 68, 0, 0, 3172, 3174, 1, 0, 0, 0, 3173, 3158, 1, 0, 0, 0, 3173, 3167, 1, 0, 0, 0, 3174, 678, 1, 0, 0, 0, 3175, 3177, 3, 689, 344, 0, 3176, 3175, 1, 0, 0, 0, 3177, 3178, 1, 0, 0, 0, 3178, 3176, 1, 0, 0, 0, 3178, 3179, 1, 0, 0, 0, 3179, 3181, 1, 0, 0, 0, 3180, 3182, 3, 687, 343, 0, 3181, 3180, 1, 0, 0, 0, 3181, 3182, 1, 0, 0, 0, 3182, 3183, 1, 0, 0, 0, 3183, 3184, 5, 66, 0, 0, 3184, 3185, 5, 68, 0, 0, 3185, 3194, 1, 0, 0, 0, 3186, 3188, 3, 685, 342, 0, 3187, 3189, 3, 687, 343, 0, 3188, 3187, 1, 0, 0, 0, 3188, 3189, 1, 0, 0, 0, 3189, 3190, 1, 0, 0, 0, 3190, 3191, 5, 66, 0, 0, 3191, 3192, 5, 68, 0, 0, 3192, 3194, 1, 0, 0, 0, 3193, 3176, 1, 0, 0, 0, 3193, 3186, 1, 0, 0, 0, 3194, 680, 1, 0, 0, 0, 3195, 3199, 3, 691, 345, 0, 3196, 3199, 3, 689, 344, 0, 3197, 3199, 5, 95, 0, 0, 3198, 3195, 1, 0, 0, 0, 3198, 3196, 1, 0, 0, 0, 3198, 3197, 1, 0, 0, 0, 3199, 3200, 1, 0, 0, 0, 3200, 3198, 1, 0, 0, 0, 3200, 3201, 1, 0, 0, 0, 3201, 682, 1, 0, 0, 0, 3202, 3208, 5, 96, 0, 0, 3203, 3207, 8, 4, 0, 0, 3204, 3205, 5, 96, 0, 0, 3205, 3207, 5, 96, 0, 0, 3206, 3203, 1, 0, 0, 0, 3206, 3204, 1, 0, 0, 0, 3207, 3210, 1, 0, 0, 0, 3208, 3206, 1, 0, 0, 0, 3208, 3209, 1, 0, 0, 0, 3209, 3211, 1, 0, 0, 0, 3210, 3208, 1, 0, 0, 0, 3211, 3212, 5, 96, 0, 0, 3212, 684, 1, 0, 0, 0, 3213, 3215, 3, 689, 344, 0, 3214, 3213, 1, 0, 0, 0, 3215, 3216, 1, 0, 0, 0, 3216, 3214, 1, 0, 0, 0, 3216, 3217, 1, 0, 0, 0, 3217, 3218, 1, 0, 0, 0, 3218, 3222, 5, 46, 0, 0, 3219, 3221, 3, 689, 344, 0, 3220, 3219, 1, 0, 0, 0, 3221, 3224, 1, 0, 0, 0, 3222, 3220, 1, 0, 0, 0, 3222, 3223, 1, 0, 0, 0, 3223, 3232, 1, 0, 0, 0, 3224, 3222, 1, 0, 0, 0, 3225, 3227, 5, 46, 0, 0, 3226, 3228, 3, 689, 344, 0, 3227, 3226, 1, 0, 0, 0, 3228, 3229, 1, 0, 0, 0, 3229, 3227, 1, 0, 0, 0, 3229, 3230, 1, 0, 0, 0, 3230, 3232, 1, 0, 0, 0, 3231, 3214, 1, 0, 0, 0, 3231, 3225, 1, 0, 0, 0, 3232, 686, 1, 0, 0, 0, 3233, 3235, 5, 69, 0, 0, 3234, 3236, 7, 5, 0, 0, 3235, 3234, 1, 0, 0, 0, 3235, 3236, 1, 0, 0, 0, 3236, 3238, 1, 0, 0, 0, 3237, 3239, 3, 689, 344, 0, 3238, 3237, 1, 0, 0, 0, 3239, 3240, 1, 0, 0, 0, 3240, 3238, 1, 0, 0, 0, 3240, 3241, 1, 0, 0, 0, 3241, 688, 1, 0, 0, 0, 3242, 3243, 7, 6, 0, 0, 3243, 690, 1, 0, 0, 0, 3244, 3245, 7, 7, 0, 0, 3245, 692, 1, 0, 0, 0, 3246, 3247, 5, 45, 0, 0, 3247, 3248, 5, 45, 0, 0, 3248, 3254, 1, 0, 0, 0, 3249, 3250, 5, 92, 0, 0, 3250, 3253, 5, 10, 0, 0, 3251, 3253, 8, 8, 0, 0, 3252, 3249, 1, 0, 0, 0, 3252, 3251, 1, 0, 0, 0, 3253, 3256, 1, 0, 0, 0, 3254, 3252, 1, 0, 0, 0, 3254, 3255, 1, 0, 0, 0, 3255, 3258, 1, 0, 0, 0, 3256, 3254, 1, 0, 0, 0, 3257, 3259, 5, 13, 0, 0, 3258, 3257, 1, 0, 0, 0, 3258, 3259, 1, 0, 0, 0, 3259, 3261, 1, 0, 0, 0, 3260, 3262, 5, 10, 0, 0, 3261, 3260, 1, 0, 0, 0, 3261, 3262, 1, 0, 0, 0, 3262, 3263, 1, 0, 0, 0, 3263, 3264, 6, 346, 0, 0, 3264, 694, 1, 0, 0, 0, 3265, 3266, 5, 47, 0, 0, 3266, 3267, 5, 42, 0, 0, 3267, 3272, 1, 0, 0, 0, 3268, 3271, 3, 695, 347, 0, 3269, 3271, 9, 0, 0, 0, 3270, 3268, 1, 0, 0, 0, 3270, 3269, 1, 0, 0, 0, 3271, 3274, 1, 0, 0, 0, 3272, 3273, 1, 0, 0, 0, 3272, 3270, 1, 0, 0, 0, 3273, 3279, 1, 0, 0, 0, 3274, 3272, 1, 0, 0, 0, 3275, 3276, 5, 42, 0, 0, 3276, 3280, 5, 47, 0, 0, 3277, 3278, 6, 347, 1, 0, 3278, 3280, 5, 0, 0, 1, 3279, 3275, 1, 0, 0, 0, 3279, 3277, 1, 0, 0, 0, 3280, 3281, 1, 0, 0, 0, 3281, 3282, 6, 347, 0, 0, 3282, 696, 1, 0, 0, 0, 3283, 3285, 7, 9, 0, 0, 3284, 3283, 1, 0, 0, 0, 3285, 3286, 1, 0, 0, 0, 3286, 3284, 1, 0, 0, 0, 3286, 3287, 1, 0, 0, 0, 3287, 3288, 1, 0, 0, 0, 3288, 3289, 6, 348, 0, 0, 3289, 698, 1, 0, 0, 0, 3290, 3291, 9, 0, 0, 0, 3291, 700, 1, 0, 0, 0, 50, 0, 1935, 2347, 2671, 2995, 3013, 3021, 3060, 3062, 3072, 3082, 3086, 3092, 3094, 3102, 3109, 3116, 3123, 3128, 3135, 3142, 3145, 3151, 3155, 3160, 3163, 3169, 3173, 3178, 3181, 3188, 3193, 3198, 3200, 3206, 3208, 3216, 3222, 3229, 3231, 3235, 3240, 3252, 3254, 3258, 3261, 3270, 3272, 3279, 3286, 2, 0, 1, 0, 1, 347, 0] diff --git a/datajunction-server/datajunction_server/sql/parsing/backends/grammar/generated/SqlBaseLexer.py b/datajunction-server/datajunction_server/sql/parsing/backends/grammar/generated/SqlBaseLexer.py new file mode 100644 index 000000000..e6f1efea9 --- /dev/null +++ b/datajunction-server/datajunction_server/sql/parsing/backends/grammar/generated/SqlBaseLexer.py @@ -0,0 +1,1836 @@ +# Generated from SqlBaseLexer.g4 by ANTLR 4.12.0 +from antlr4 import * +from io import StringIO +import sys +if sys.version_info[1] > 5: + from typing import TextIO +else: + from typing.io import TextIO + + +def serializedATN(): + return [ + 4,0,346,3292,6,-1,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7, + 5,2,6,7,6,2,7,7,7,2,8,7,8,2,9,7,9,2,10,7,10,2,11,7,11,2,12,7,12, + 2,13,7,13,2,14,7,14,2,15,7,15,2,16,7,16,2,17,7,17,2,18,7,18,2,19, + 7,19,2,20,7,20,2,21,7,21,2,22,7,22,2,23,7,23,2,24,7,24,2,25,7,25, + 2,26,7,26,2,27,7,27,2,28,7,28,2,29,7,29,2,30,7,30,2,31,7,31,2,32, + 7,32,2,33,7,33,2,34,7,34,2,35,7,35,2,36,7,36,2,37,7,37,2,38,7,38, + 2,39,7,39,2,40,7,40,2,41,7,41,2,42,7,42,2,43,7,43,2,44,7,44,2,45, + 7,45,2,46,7,46,2,47,7,47,2,48,7,48,2,49,7,49,2,50,7,50,2,51,7,51, + 2,52,7,52,2,53,7,53,2,54,7,54,2,55,7,55,2,56,7,56,2,57,7,57,2,58, + 7,58,2,59,7,59,2,60,7,60,2,61,7,61,2,62,7,62,2,63,7,63,2,64,7,64, + 2,65,7,65,2,66,7,66,2,67,7,67,2,68,7,68,2,69,7,69,2,70,7,70,2,71, + 7,71,2,72,7,72,2,73,7,73,2,74,7,74,2,75,7,75,2,76,7,76,2,77,7,77, + 2,78,7,78,2,79,7,79,2,80,7,80,2,81,7,81,2,82,7,82,2,83,7,83,2,84, + 7,84,2,85,7,85,2,86,7,86,2,87,7,87,2,88,7,88,2,89,7,89,2,90,7,90, + 2,91,7,91,2,92,7,92,2,93,7,93,2,94,7,94,2,95,7,95,2,96,7,96,2,97, + 7,97,2,98,7,98,2,99,7,99,2,100,7,100,2,101,7,101,2,102,7,102,2,103, + 7,103,2,104,7,104,2,105,7,105,2,106,7,106,2,107,7,107,2,108,7,108, + 2,109,7,109,2,110,7,110,2,111,7,111,2,112,7,112,2,113,7,113,2,114, + 7,114,2,115,7,115,2,116,7,116,2,117,7,117,2,118,7,118,2,119,7,119, + 2,120,7,120,2,121,7,121,2,122,7,122,2,123,7,123,2,124,7,124,2,125, + 7,125,2,126,7,126,2,127,7,127,2,128,7,128,2,129,7,129,2,130,7,130, + 2,131,7,131,2,132,7,132,2,133,7,133,2,134,7,134,2,135,7,135,2,136, + 7,136,2,137,7,137,2,138,7,138,2,139,7,139,2,140,7,140,2,141,7,141, + 2,142,7,142,2,143,7,143,2,144,7,144,2,145,7,145,2,146,7,146,2,147, + 7,147,2,148,7,148,2,149,7,149,2,150,7,150,2,151,7,151,2,152,7,152, + 2,153,7,153,2,154,7,154,2,155,7,155,2,156,7,156,2,157,7,157,2,158, + 7,158,2,159,7,159,2,160,7,160,2,161,7,161,2,162,7,162,2,163,7,163, + 2,164,7,164,2,165,7,165,2,166,7,166,2,167,7,167,2,168,7,168,2,169, + 7,169,2,170,7,170,2,171,7,171,2,172,7,172,2,173,7,173,2,174,7,174, + 2,175,7,175,2,176,7,176,2,177,7,177,2,178,7,178,2,179,7,179,2,180, + 7,180,2,181,7,181,2,182,7,182,2,183,7,183,2,184,7,184,2,185,7,185, + 2,186,7,186,2,187,7,187,2,188,7,188,2,189,7,189,2,190,7,190,2,191, + 7,191,2,192,7,192,2,193,7,193,2,194,7,194,2,195,7,195,2,196,7,196, + 2,197,7,197,2,198,7,198,2,199,7,199,2,200,7,200,2,201,7,201,2,202, + 7,202,2,203,7,203,2,204,7,204,2,205,7,205,2,206,7,206,2,207,7,207, + 2,208,7,208,2,209,7,209,2,210,7,210,2,211,7,211,2,212,7,212,2,213, + 7,213,2,214,7,214,2,215,7,215,2,216,7,216,2,217,7,217,2,218,7,218, + 2,219,7,219,2,220,7,220,2,221,7,221,2,222,7,222,2,223,7,223,2,224, + 7,224,2,225,7,225,2,226,7,226,2,227,7,227,2,228,7,228,2,229,7,229, + 2,230,7,230,2,231,7,231,2,232,7,232,2,233,7,233,2,234,7,234,2,235, + 7,235,2,236,7,236,2,237,7,237,2,238,7,238,2,239,7,239,2,240,7,240, + 2,241,7,241,2,242,7,242,2,243,7,243,2,244,7,244,2,245,7,245,2,246, + 7,246,2,247,7,247,2,248,7,248,2,249,7,249,2,250,7,250,2,251,7,251, + 2,252,7,252,2,253,7,253,2,254,7,254,2,255,7,255,2,256,7,256,2,257, + 7,257,2,258,7,258,2,259,7,259,2,260,7,260,2,261,7,261,2,262,7,262, + 2,263,7,263,2,264,7,264,2,265,7,265,2,266,7,266,2,267,7,267,2,268, + 7,268,2,269,7,269,2,270,7,270,2,271,7,271,2,272,7,272,2,273,7,273, + 2,274,7,274,2,275,7,275,2,276,7,276,2,277,7,277,2,278,7,278,2,279, + 7,279,2,280,7,280,2,281,7,281,2,282,7,282,2,283,7,283,2,284,7,284, + 2,285,7,285,2,286,7,286,2,287,7,287,2,288,7,288,2,289,7,289,2,290, + 7,290,2,291,7,291,2,292,7,292,2,293,7,293,2,294,7,294,2,295,7,295, + 2,296,7,296,2,297,7,297,2,298,7,298,2,299,7,299,2,300,7,300,2,301, + 7,301,2,302,7,302,2,303,7,303,2,304,7,304,2,305,7,305,2,306,7,306, + 2,307,7,307,2,308,7,308,2,309,7,309,2,310,7,310,2,311,7,311,2,312, + 7,312,2,313,7,313,2,314,7,314,2,315,7,315,2,316,7,316,2,317,7,317, + 2,318,7,318,2,319,7,319,2,320,7,320,2,321,7,321,2,322,7,322,2,323, + 7,323,2,324,7,324,2,325,7,325,2,326,7,326,2,327,7,327,2,328,7,328, + 2,329,7,329,2,330,7,330,2,331,7,331,2,332,7,332,2,333,7,333,2,334, + 7,334,2,335,7,335,2,336,7,336,2,337,7,337,2,338,7,338,2,339,7,339, + 2,340,7,340,2,341,7,341,2,342,7,342,2,343,7,343,2,344,7,344,2,345, + 7,345,2,346,7,346,2,347,7,347,2,348,7,348,2,349,7,349,1,0,1,0,1, + 1,1,1,1,2,1,2,1,3,1,3,1,4,1,4,1,5,1,5,1,6,1,6,1,7,1,7,1,7,1,7,1, + 8,1,8,1,8,1,8,1,8,1,8,1,9,1,9,1,9,1,9,1,10,1,10,1,10,1,10,1,10,1, + 10,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,12,1,12,1,12,1,12,1,12,1, + 12,1,12,1,12,1,13,1,13,1,13,1,13,1,14,1,14,1,14,1,14,1,14,1,15,1, + 15,1,15,1,15,1,16,1,16,1,16,1,16,1,16,1,16,1,16,1,16,1,16,1,16,1, + 17,1,17,1,17,1,17,1,17,1,17,1,17,1,17,1,18,1,18,1,18,1,18,1,18,1, + 18,1,19,1,19,1,19,1,20,1,20,1,20,1,20,1,21,1,21,1,21,1,22,1,22,1, + 22,1,22,1,22,1,22,1,22,1,22,1,22,1,22,1,22,1,22,1,22,1,22,1,23,1, + 23,1,23,1,23,1,23,1,23,1,23,1,23,1,24,1,24,1,24,1,24,1,24,1,25,1, + 25,1,25,1,25,1,25,1,25,1,25,1,26,1,26,1,26,1,26,1,26,1,26,1,26,1, + 26,1,27,1,27,1,27,1,28,1,28,1,28,1,28,1,28,1,28,1,29,1,29,1,29,1, + 29,1,29,1,29,1,29,1,29,1,30,1,30,1,30,1,30,1,30,1,31,1,31,1,31,1, + 31,1,31,1,32,1,32,1,32,1,32,1,32,1,32,1,32,1,32,1,33,1,33,1,33,1, + 33,1,33,1,33,1,33,1,33,1,33,1,34,1,34,1,34,1,34,1,34,1,34,1,34,1, + 35,1,35,1,35,1,35,1,35,1,35,1,36,1,36,1,36,1,36,1,36,1,36,1,37,1, + 37,1,37,1,37,1,37,1,37,1,37,1,37,1,38,1,38,1,38,1,38,1,38,1,38,1, + 38,1,38,1,38,1,38,1,39,1,39,1,39,1,39,1,39,1,39,1,39,1,39,1,40,1, + 40,1,40,1,40,1,40,1,40,1,40,1,40,1,41,1,41,1,41,1,41,1,41,1,41,1, + 41,1,41,1,41,1,41,1,41,1,42,1,42,1,42,1,42,1,42,1,42,1,42,1,43,1, + 43,1,43,1,43,1,43,1,43,1,43,1,43,1,44,1,44,1,44,1,44,1,44,1,44,1, + 44,1,44,1,45,1,45,1,45,1,45,1,45,1,45,1,45,1,46,1,46,1,46,1,46,1, + 46,1,46,1,46,1,46,1,47,1,47,1,47,1,47,1,47,1,47,1,47,1,47,1,47,1, + 47,1,47,1,47,1,48,1,48,1,48,1,48,1,48,1,48,1,48,1,48,1,49,1,49,1, + 49,1,49,1,49,1,49,1,49,1,49,1,49,1,49,1,49,1,49,1,50,1,50,1,50,1, + 50,1,50,1,50,1,50,1,50,1,50,1,50,1,50,1,51,1,51,1,51,1,51,1,51,1, + 52,1,52,1,52,1,52,1,52,1,52,1,52,1,53,1,53,1,53,1,53,1,53,1,53,1, + 54,1,54,1,54,1,54,1,54,1,55,1,55,1,55,1,55,1,55,1,55,1,55,1,55,1, + 56,1,56,1,56,1,56,1,56,1,56,1,56,1,56,1,56,1,56,1,56,1,56,1,56,1, + 57,1,57,1,57,1,57,1,57,1,57,1,57,1,57,1,57,1,57,1,57,1,57,1,57,1, + 58,1,58,1,58,1,58,1,58,1,58,1,58,1,58,1,58,1,58,1,58,1,58,1,58,1, + 58,1,58,1,58,1,58,1,58,1,59,1,59,1,59,1,59,1,59,1,59,1,59,1,59,1, + 59,1,59,1,59,1,59,1,59,1,60,1,60,1,60,1,60,1,61,1,61,1,61,1,61,1, + 61,1,62,1,62,1,62,1,62,1,62,1,62,1,62,1,62,1,62,1,62,1,63,1,63,1, + 63,1,63,1,63,1,64,1,64,1,64,1,64,1,64,1,64,1,64,1,64,1,64,1,65,1, + 65,1,65,1,65,1,65,1,65,1,65,1,65,1,65,1,65,1,66,1,66,1,66,1,66,1, + 66,1,66,1,66,1,66,1,67,1,67,1,67,1,67,1,67,1,67,1,67,1,67,1,67,1, + 68,1,68,1,68,1,68,1,68,1,68,1,68,1,68,1,68,1,68,1,68,1,68,1,68,1, + 69,1,69,1,69,1,69,1,69,1,69,1,69,1,69,1,70,1,70,1,70,1,70,1,70,1, + 70,1,70,1,70,1,71,1,71,1,71,1,71,1,71,1,71,1,71,1,72,1,72,1,72,1, + 72,1,72,1,72,1,72,1,72,1,72,1,72,1,73,1,73,1,73,1,73,1,73,1,74,1, + 74,1,74,1,74,1,74,1,74,1,74,1,74,1,74,1,75,1,75,1,75,1,75,1,76,1, + 76,1,76,1,76,1,76,1,76,1,76,1,76,1,76,1,76,1,76,1,76,1,77,1,77,1, + 77,1,77,1,77,1,77,1,77,1,77,1,77,1,77,1,78,1,78,1,78,1,78,1,78,1, + 78,1,78,1,78,1,78,1,79,1,79,1,79,1,79,1,79,1,79,1,79,1,79,1,79,1, + 79,1,79,1,80,1,80,1,80,1,80,1,81,1,81,1,81,1,81,1,81,1,82,1,82,1, + 82,1,82,1,82,1,83,1,83,1,83,1,83,1,84,1,84,1,84,1,84,1,84,1,84,1, + 84,1,85,1,85,1,85,1,85,1,85,1,85,1,85,1,85,1,86,1,86,1,86,1,86,1, + 86,1,86,1,86,1,87,1,87,1,87,1,87,1,87,1,87,1,87,1,87,1,87,1,88,1, + 88,1,88,1,88,1,88,1,88,1,88,1,88,1,89,1,89,1,89,1,89,1,89,1,89,1, + 89,1,90,1,90,1,90,1,90,1,90,1,90,1,90,1,90,1,91,1,91,1,91,1,91,1, + 91,1,91,1,91,1,92,1,92,1,92,1,92,1,92,1,92,1,92,1,92,1,92,1,93,1, + 93,1,93,1,93,1,93,1,93,1,93,1,93,1,93,1,94,1,94,1,94,1,94,1,94,1, + 94,1,94,1,94,1,95,1,95,1,95,1,95,1,95,1,95,1,96,1,96,1,96,1,96,1, + 96,1,96,1,97,1,97,1,97,1,97,1,97,1,97,1,97,1,98,1,98,1,98,1,98,1, + 98,1,98,1,98,1,99,1,99,1,99,1,99,1,99,1,99,1,99,1,99,1,99,1,99,1, + 99,1,100,1,100,1,100,1,100,1,100,1,100,1,101,1,101,1,101,1,101,1, + 101,1,101,1,101,1,101,1,101,1,101,1,102,1,102,1,102,1,102,1,103, + 1,103,1,103,1,103,1,103,1,103,1,103,1,103,1,104,1,104,1,104,1,104, + 1,104,1,104,1,104,1,105,1,105,1,105,1,105,1,105,1,105,1,105,1,105, + 1,105,1,105,1,106,1,106,1,106,1,106,1,106,1,107,1,107,1,107,1,107, + 1,107,1,108,1,108,1,108,1,108,1,108,1,108,1,108,1,108,1,108,1,109, + 1,109,1,109,1,109,1,109,1,109,1,109,1,109,1,109,1,109,1,110,1,110, + 1,110,1,110,1,110,1,110,1,110,1,110,1,110,1,110,1,111,1,111,1,111, + 1,111,1,111,1,111,1,111,1,112,1,112,1,112,1,112,1,112,1,112,1,113, + 1,113,1,113,1,113,1,113,1,113,1,114,1,114,1,114,1,114,1,114,1,114, + 1,114,1,114,1,114,1,115,1,115,1,115,1,115,1,115,1,115,1,115,1,116, + 1,116,1,116,1,116,1,116,1,117,1,117,1,117,1,117,1,117,1,117,1,118, + 1,118,1,118,1,119,1,119,1,119,1,119,1,119,1,119,1,119,1,120,1,120, + 1,120,1,120,1,120,1,120,1,120,1,121,1,121,1,121,1,122,1,122,1,122, + 1,122,1,122,1,122,1,122,1,122,1,123,1,123,1,123,1,123,1,123,1,123, + 1,124,1,124,1,124,1,124,1,124,1,124,1,124,1,124,1,125,1,125,1,125, + 1,125,1,125,1,125,1,126,1,126,1,126,1,126,1,126,1,126,1,126,1,127, + 1,127,1,127,1,127,1,127,1,127,1,127,1,127,1,127,1,127,1,127,1,127, + 1,128,1,128,1,128,1,128,1,128,1,128,1,128,1,129,1,129,1,129,1,129, + 1,129,1,129,1,129,1,129,1,129,1,129,1,130,1,130,1,130,1,130,1,130, + 1,130,1,130,1,130,1,130,1,131,1,131,1,131,1,131,1,131,1,132,1,132, + 1,132,1,133,1,133,1,133,1,133,1,133,1,133,1,134,1,134,1,134,1,134, + 1,134,1,135,1,135,1,135,1,135,1,135,1,136,1,136,1,136,1,136,1,136, + 1,137,1,137,1,137,1,137,1,137,1,137,1,137,1,137,1,138,1,138,1,138, + 1,138,1,138,1,139,1,139,1,139,1,139,1,139,1,139,1,139,1,139,1,140, + 1,140,1,140,1,140,1,140,1,141,1,141,1,141,1,141,1,141,1,142,1,142, + 1,142,1,142,1,142,1,142,1,143,1,143,1,143,1,143,1,143,1,143,1,144, + 1,144,1,144,1,144,1,144,1,144,1,145,1,145,1,145,1,145,1,145,1,146, + 1,146,1,146,1,146,1,146,1,147,1,147,1,147,1,147,1,147,1,147,1,148, + 1,148,1,148,1,148,1,148,1,148,1,148,1,148,1,148,1,149,1,149,1,149, + 1,149,1,149,1,150,1,150,1,150,1,150,1,150,1,150,1,151,1,151,1,151, + 1,151,1,151,1,151,1,151,1,151,1,152,1,152,1,152,1,152,1,152,1,152, + 1,153,1,153,1,153,1,153,1,154,1,154,1,154,1,154,1,154,1,154,1,154, + 1,154,1,155,1,155,1,155,1,155,1,155,1,155,1,156,1,156,1,156,1,156, + 1,156,1,156,1,156,1,156,1,156,1,156,1,156,1,156,1,157,1,157,1,157, + 1,157,1,157,1,157,1,157,1,157,1,157,1,157,1,157,1,157,1,157,1,158, + 1,158,1,158,1,158,1,158,1,158,1,158,1,158,1,158,1,158,1,158,1,158, + 1,159,1,159,1,159,1,159,1,159,1,159,1,159,1,159,1,159,1,159,1,159, + 1,159,1,159,1,160,1,160,1,160,1,160,1,160,1,160,1,160,1,161,1,161, + 1,161,1,161,1,161,1,161,1,161,1,161,1,162,1,162,1,162,1,162,1,162, + 1,162,1,163,1,163,1,163,1,163,1,163,1,163,1,163,1,164,1,164,1,164, + 1,164,1,164,1,165,1,165,1,165,1,165,1,165,1,165,1,165,1,165,1,165, + 1,165,1,166,1,166,1,166,1,166,1,166,1,166,1,166,1,166,1,166,1,166, + 1,166,1,167,1,167,1,167,1,167,1,167,1,167,1,167,1,167,1,167,1,167, + 1,167,1,168,1,168,1,168,1,168,1,168,1,168,1,168,1,168,1,168,1,168, + 1,168,1,168,1,169,1,169,1,169,1,169,1,169,1,169,1,169,1,169,1,170, + 1,170,1,170,1,171,1,171,1,171,1,171,3,171,1936,8,171,1,172,1,172, + 1,172,1,172,1,172,1,173,1,173,1,173,1,173,1,173,1,173,1,174,1,174, + 1,174,1,175,1,175,1,175,1,175,1,175,1,175,1,175,1,176,1,176,1,176, + 1,177,1,177,1,177,1,177,1,177,1,178,1,178,1,178,1,178,1,178,1,178, + 1,178,1,179,1,179,1,179,1,179,1,179,1,179,1,179,1,179,1,180,1,180, + 1,180,1,181,1,181,1,181,1,181,1,181,1,181,1,182,1,182,1,182,1,182, + 1,183,1,183,1,183,1,183,1,183,1,183,1,184,1,184,1,184,1,184,1,184, + 1,184,1,184,1,184,1,184,1,184,1,184,1,184,1,184,1,185,1,185,1,185, + 1,185,1,185,1,186,1,186,1,186,1,186,1,186,1,186,1,186,1,186,1,186, + 1,187,1,187,1,187,1,187,1,187,1,187,1,187,1,187,1,188,1,188,1,188, + 1,188,1,188,1,188,1,188,1,188,1,188,1,188,1,189,1,189,1,189,1,189, + 1,189,1,189,1,189,1,189,1,189,1,189,1,190,1,190,1,190,1,190,1,190, + 1,190,1,190,1,190,1,190,1,190,1,190,1,190,1,191,1,191,1,191,1,191, + 1,191,1,191,1,191,1,191,1,191,1,191,1,191,1,192,1,192,1,192,1,192, + 1,192,1,192,1,192,1,192,1,192,1,192,1,192,1,192,1,192,1,192,1,192, + 1,192,1,193,1,193,1,193,1,193,1,193,1,193,1,193,1,193,1,193,1,193, + 1,193,1,193,1,193,1,193,1,193,1,193,1,194,1,194,1,194,1,194,1,194, + 1,194,1,194,1,194,1,195,1,195,1,195,1,195,1,195,1,195,1,196,1,196, + 1,196,1,196,1,196,1,196,1,196,1,196,1,197,1,197,1,197,1,197,1,197, + 1,197,1,197,1,197,1,197,1,198,1,198,1,198,1,198,1,198,1,198,1,198, + 1,198,1,198,1,198,1,199,1,199,1,199,1,199,1,199,1,199,1,199,1,199, + 1,200,1,200,1,200,1,200,1,200,1,200,1,200,1,200,1,200,1,200,1,200, + 1,201,1,201,1,201,1,201,1,201,1,201,1,201,1,201,1,201,1,201,1,201, + 1,202,1,202,1,202,1,202,1,202,1,202,1,203,1,203,1,203,1,203,1,203, + 1,203,1,203,1,203,1,204,1,204,1,204,1,204,1,204,1,204,1,205,1,205, + 1,205,1,205,1,205,1,205,1,206,1,206,1,206,1,206,1,206,1,206,1,206, + 1,206,1,206,1,206,1,206,1,206,1,206,1,207,1,207,1,207,1,207,1,207, + 1,207,1,207,1,207,1,207,1,207,1,207,1,207,1,207,1,208,1,208,1,208, + 1,208,1,208,1,208,1,208,1,208,1,209,1,209,1,209,1,209,1,209,1,209, + 1,209,1,210,1,210,1,210,1,210,1,210,1,210,1,210,1,210,1,210,1,210, + 1,210,1,211,1,211,1,211,1,211,1,211,1,211,1,211,1,211,1,212,1,212, + 1,212,1,212,1,212,1,212,1,212,1,213,1,213,1,213,1,213,1,213,1,213, + 1,213,1,214,1,214,1,214,1,214,1,214,1,214,1,214,1,214,1,214,1,214, + 1,214,1,215,1,215,1,215,1,215,1,215,1,215,1,215,1,215,1,216,1,216, + 1,216,1,216,1,216,1,216,1,217,1,217,1,217,1,217,1,217,1,217,1,217, + 1,217,1,218,1,218,1,218,1,218,1,218,1,218,1,218,1,218,1,218,1,219, + 1,219,1,219,1,219,1,219,1,219,1,219,1,220,1,220,1,220,1,220,1,220, + 1,220,1,221,1,221,1,221,1,221,1,221,1,221,1,221,1,221,1,221,1,221, + 1,221,3,221,2348,8,221,1,222,1,222,1,222,1,222,1,222,1,223,1,223, + 1,223,1,223,1,223,1,223,1,224,1,224,1,224,1,224,1,224,1,224,1,224, + 1,224,1,224,1,225,1,225,1,225,1,225,1,225,1,225,1,225,1,226,1,226, + 1,226,1,226,1,227,1,227,1,227,1,227,1,227,1,228,1,228,1,228,1,228, + 1,228,1,228,1,228,1,229,1,229,1,229,1,229,1,229,1,229,1,229,1,229, + 1,230,1,230,1,230,1,230,1,230,1,230,1,230,1,231,1,231,1,231,1,231, + 1,231,1,231,1,231,1,231,1,232,1,232,1,232,1,232,1,232,1,232,1,232, + 1,233,1,233,1,233,1,233,1,233,1,234,1,234,1,234,1,234,1,234,1,234, + 1,234,1,234,1,234,1,234,1,235,1,235,1,235,1,235,1,235,1,235,1,236, + 1,236,1,236,1,236,1,236,1,236,1,236,1,236,1,236,1,236,1,236,1,236, + 1,236,1,236,1,236,1,236,1,237,1,237,1,237,1,237,1,237,1,237,1,237, + 1,237,1,237,1,237,1,237,1,237,1,237,1,238,1,238,1,238,1,238,1,239, + 1,239,1,239,1,239,1,239,1,239,1,240,1,240,1,240,1,240,1,240,1,241, + 1,241,1,241,1,241,1,241,1,242,1,242,1,242,1,242,1,242,1,242,1,242, + 1,243,1,243,1,243,1,243,1,243,1,244,1,244,1,244,1,244,1,244,1,245, + 1,245,1,245,1,245,1,245,1,245,1,245,1,246,1,246,1,246,1,246,1,246, + 1,246,1,246,1,247,1,247,1,247,1,247,1,247,1,247,1,248,1,248,1,248, + 1,248,1,248,1,248,1,248,1,248,1,248,1,248,1,248,1,249,1,249,1,249, + 1,249,1,249,1,249,1,249,1,250,1,250,1,250,1,250,1,250,1,250,1,250, + 1,250,1,250,1,251,1,251,1,251,1,251,1,251,1,251,1,251,1,252,1,252, + 1,252,1,252,1,252,1,252,1,252,1,253,1,253,1,253,1,253,1,253,1,253, + 1,253,1,253,1,253,1,253,1,254,1,254,1,254,1,254,1,254,1,255,1,255, + 1,255,1,255,1,255,1,255,1,255,1,255,1,255,1,255,1,255,1,255,1,256, + 1,256,1,256,1,256,1,256,1,256,1,256,1,256,1,256,1,256,1,256,1,256, + 1,256,1,256,1,256,1,257,1,257,1,257,1,257,1,257,1,257,1,258,1,258, + 1,258,1,258,1,258,1,258,1,258,1,259,1,259,1,259,1,259,1,259,1,259, + 1,259,1,259,1,259,1,259,1,259,1,259,1,260,1,260,1,260,1,260,1,260, + 1,260,1,260,1,261,1,261,1,261,1,261,1,261,1,261,1,261,1,261,1,261, + 1,261,1,261,1,261,1,261,1,261,1,262,1,262,1,262,1,262,1,262,1,262, + 1,262,1,262,1,262,1,262,1,262,1,262,1,262,3,262,2672,8,262,1,263, + 1,263,1,263,1,263,1,263,1,263,1,263,1,263,1,263,1,263,1,263,1,264, + 1,264,1,264,1,264,1,264,1,265,1,265,1,265,1,265,1,265,1,266,1,266, + 1,266,1,266,1,266,1,266,1,266,1,266,1,266,1,266,1,267,1,267,1,267, + 1,267,1,267,1,267,1,267,1,267,1,267,1,267,1,267,1,267,1,267,1,268, + 1,268,1,268,1,268,1,268,1,268,1,268,1,268,1,268,1,268,1,268,1,268, + 1,268,1,268,1,269,1,269,1,269,1,270,1,270,1,270,1,270,1,270,1,270, + 1,271,1,271,1,271,1,271,1,271,1,271,1,271,1,271,1,271,1,272,1,272, + 1,272,1,272,1,272,1,272,1,272,1,272,1,272,1,272,1,272,1,272,1,273, + 1,273,1,273,1,273,1,273,1,273,1,273,1,273,1,273,1,273,1,273,1,273, + 1,273,1,274,1,274,1,274,1,274,1,274,1,274,1,274,1,274,1,274,1,274, + 1,275,1,275,1,275,1,275,1,275,1,276,1,276,1,276,1,276,1,276,1,277, + 1,277,1,277,1,277,1,277,1,277,1,277,1,277,1,277,1,278,1,278,1,278, + 1,278,1,278,1,278,1,278,1,278,1,278,1,279,1,279,1,279,1,279,1,279, + 1,280,1,280,1,280,1,280,1,280,1,280,1,280,1,280,1,280,1,280,1,281, + 1,281,1,281,1,281,1,281,1,281,1,281,1,281,1,281,1,281,1,282,1,282, + 1,282,1,282,1,282,1,282,1,282,1,282,1,283,1,283,1,283,1,283,1,283, + 1,283,1,284,1,284,1,284,1,284,1,284,1,284,1,284,1,285,1,285,1,285, + 1,285,1,285,1,285,1,285,1,285,1,286,1,286,1,286,1,286,1,286,1,286, + 1,286,1,287,1,287,1,287,1,287,1,287,1,287,1,287,1,287,1,288,1,288, + 1,288,1,288,1,288,1,288,1,289,1,289,1,289,1,289,1,289,1,289,1,289, + 1,290,1,290,1,290,1,290,1,291,1,291,1,291,1,291,1,291,1,292,1,292, + 1,292,1,292,1,292,1,292,1,293,1,293,1,293,1,293,1,293,1,293,1,293, + 1,294,1,294,1,294,1,294,1,294,1,294,1,294,1,294,1,295,1,295,1,295, + 1,295,1,295,1,296,1,296,1,296,1,296,1,296,1,296,1,297,1,297,1,297, + 1,297,1,297,1,298,1,298,1,298,1,298,1,298,1,298,1,299,1,299,1,299, + 1,299,1,299,1,300,1,300,1,300,1,300,1,300,1,300,1,301,1,301,1,301, + 1,301,1,301,1,301,1,301,1,302,1,302,1,302,1,302,1,302,1,303,1,303, + 1,303,1,303,1,303,1,303,1,303,1,304,1,304,1,304,1,304,1,304,1,305, + 1,305,1,305,1,305,1,305,1,305,1,306,1,306,1,306,1,306,1,306,1,307, + 1,307,1,307,3,307,2996,8,307,1,308,1,308,1,308,1,308,1,309,1,309, + 1,309,1,310,1,310,1,310,1,311,1,311,1,312,1,312,1,312,1,312,3,312, + 3014,8,312,1,313,1,313,1,314,1,314,1,314,1,314,3,314,3022,8,314, + 1,315,1,315,1,316,1,316,1,317,1,317,1,318,1,318,1,319,1,319,1,320, + 1,320,1,321,1,321,1,322,1,322,1,323,1,323,1,323,1,324,1,324,1,325, + 1,325,1,326,1,326,1,326,1,327,1,327,1,327,1,327,1,328,1,328,1,328, + 1,329,1,329,1,329,1,329,5,329,3061,8,329,10,329,12,329,3064,9,329, + 1,329,1,329,1,329,1,329,1,329,5,329,3071,8,329,10,329,12,329,3074, + 9,329,1,329,1,329,1,329,1,329,1,329,5,329,3081,8,329,10,329,12,329, + 3084,9,329,1,329,3,329,3087,8,329,1,330,1,330,1,330,1,330,5,330, + 3093,8,330,10,330,12,330,3096,9,330,1,330,1,330,1,331,4,331,3101, + 8,331,11,331,12,331,3102,1,331,1,331,1,332,4,332,3108,8,332,11,332, + 12,332,3109,1,332,1,332,1,333,4,333,3115,8,333,11,333,12,333,3116, + 1,333,1,333,1,334,4,334,3122,8,334,11,334,12,334,3123,1,335,4,335, + 3127,8,335,11,335,12,335,3128,1,335,1,335,1,335,1,335,1,335,3,335, + 3136,8,335,1,336,1,336,1,337,4,337,3141,8,337,11,337,12,337,3142, + 1,337,3,337,3146,8,337,1,337,1,337,1,337,1,337,3,337,3152,8,337, + 1,337,1,337,3,337,3156,8,337,1,338,4,338,3159,8,338,11,338,12,338, + 3160,1,338,3,338,3164,8,338,1,338,1,338,1,338,1,338,3,338,3170,8, + 338,1,338,1,338,3,338,3174,8,338,1,339,4,339,3177,8,339,11,339,12, + 339,3178,1,339,3,339,3182,8,339,1,339,1,339,1,339,1,339,1,339,3, + 339,3189,8,339,1,339,1,339,1,339,3,339,3194,8,339,1,340,1,340,1, + 340,4,340,3199,8,340,11,340,12,340,3200,1,341,1,341,1,341,1,341, + 5,341,3207,8,341,10,341,12,341,3210,9,341,1,341,1,341,1,342,4,342, + 3215,8,342,11,342,12,342,3216,1,342,1,342,5,342,3221,8,342,10,342, + 12,342,3224,9,342,1,342,1,342,4,342,3228,8,342,11,342,12,342,3229, + 3,342,3232,8,342,1,343,1,343,3,343,3236,8,343,1,343,4,343,3239,8, + 343,11,343,12,343,3240,1,344,1,344,1,345,1,345,1,346,1,346,1,346, + 1,346,1,346,1,346,5,346,3253,8,346,10,346,12,346,3256,9,346,1,346, + 3,346,3259,8,346,1,346,3,346,3262,8,346,1,346,1,346,1,347,1,347, + 1,347,1,347,1,347,5,347,3271,8,347,10,347,12,347,3274,9,347,1,347, + 1,347,1,347,1,347,3,347,3280,8,347,1,347,1,347,1,348,4,348,3285, + 8,348,11,348,12,348,3286,1,348,1,348,1,349,1,349,1,3272,0,350,1, + 1,3,2,5,3,7,4,9,5,11,6,13,7,15,8,17,9,19,10,21,11,23,12,25,13,27, + 14,29,15,31,16,33,17,35,18,37,19,39,20,41,21,43,22,45,23,47,24,49, + 25,51,26,53,27,55,28,57,29,59,30,61,31,63,32,65,33,67,34,69,35,71, + 36,73,37,75,38,77,39,79,40,81,41,83,42,85,43,87,44,89,45,91,46,93, + 47,95,48,97,49,99,50,101,51,103,52,105,53,107,54,109,55,111,56,113, + 57,115,58,117,59,119,60,121,61,123,62,125,63,127,64,129,65,131,66, + 133,67,135,68,137,69,139,70,141,71,143,72,145,73,147,74,149,75,151, + 76,153,77,155,78,157,79,159,80,161,81,163,82,165,83,167,84,169,85, + 171,86,173,87,175,88,177,89,179,90,181,91,183,92,185,93,187,94,189, + 95,191,96,193,97,195,98,197,99,199,100,201,101,203,102,205,103,207, + 104,209,105,211,106,213,107,215,108,217,109,219,110,221,111,223, + 112,225,113,227,114,229,115,231,116,233,117,235,118,237,119,239, + 120,241,121,243,122,245,123,247,124,249,125,251,126,253,127,255, + 128,257,129,259,130,261,131,263,132,265,133,267,134,269,135,271, + 136,273,137,275,138,277,139,279,140,281,141,283,142,285,143,287, + 144,289,145,291,146,293,147,295,148,297,149,299,150,301,151,303, + 152,305,153,307,154,309,155,311,156,313,157,315,158,317,159,319, + 160,321,161,323,162,325,163,327,164,329,165,331,166,333,167,335, + 168,337,169,339,170,341,171,343,172,345,173,347,174,349,175,351, + 176,353,177,355,178,357,179,359,180,361,181,363,182,365,183,367, + 184,369,185,371,186,373,187,375,188,377,189,379,190,381,191,383, + 192,385,193,387,194,389,195,391,196,393,197,395,198,397,199,399, + 200,401,201,403,202,405,203,407,204,409,205,411,206,413,207,415, + 208,417,209,419,210,421,211,423,212,425,213,427,214,429,215,431, + 216,433,217,435,218,437,219,439,220,441,221,443,222,445,223,447, + 224,449,225,451,226,453,227,455,228,457,229,459,230,461,231,463, + 232,465,233,467,234,469,235,471,236,473,237,475,238,477,239,479, + 240,481,241,483,242,485,243,487,244,489,245,491,246,493,247,495, + 248,497,249,499,250,501,251,503,252,505,253,507,254,509,255,511, + 256,513,257,515,258,517,259,519,260,521,261,523,262,525,263,527, + 264,529,265,531,266,533,267,535,268,537,269,539,270,541,271,543, + 272,545,273,547,274,549,275,551,276,553,277,555,278,557,279,559, + 280,561,281,563,282,565,283,567,284,569,285,571,286,573,287,575, + 288,577,289,579,290,581,291,583,292,585,293,587,294,589,295,591, + 296,593,297,595,298,597,299,599,300,601,301,603,302,605,303,607, + 304,609,305,611,306,613,307,615,308,617,309,619,310,621,311,623, + 312,625,313,627,314,629,315,631,316,633,317,635,318,637,319,639, + 320,641,321,643,322,645,323,647,324,649,325,651,326,653,327,655, + 328,657,329,659,330,661,331,663,332,665,333,667,334,669,335,671, + 336,673,337,675,338,677,339,679,340,681,341,683,342,685,0,687,0, + 689,0,691,0,693,343,695,344,697,345,699,346,1,0,10,2,0,39,39,92, + 92,1,0,39,39,1,0,34,34,2,0,34,34,92,92,1,0,96,96,2,0,43,43,45,45, + 1,0,48,57,1,0,65,90,2,0,10,10,13,13,3,0,9,10,13,13,32,32,3338,0, + 1,1,0,0,0,0,3,1,0,0,0,0,5,1,0,0,0,0,7,1,0,0,0,0,9,1,0,0,0,0,11,1, + 0,0,0,0,13,1,0,0,0,0,15,1,0,0,0,0,17,1,0,0,0,0,19,1,0,0,0,0,21,1, + 0,0,0,0,23,1,0,0,0,0,25,1,0,0,0,0,27,1,0,0,0,0,29,1,0,0,0,0,31,1, + 0,0,0,0,33,1,0,0,0,0,35,1,0,0,0,0,37,1,0,0,0,0,39,1,0,0,0,0,41,1, + 0,0,0,0,43,1,0,0,0,0,45,1,0,0,0,0,47,1,0,0,0,0,49,1,0,0,0,0,51,1, + 0,0,0,0,53,1,0,0,0,0,55,1,0,0,0,0,57,1,0,0,0,0,59,1,0,0,0,0,61,1, + 0,0,0,0,63,1,0,0,0,0,65,1,0,0,0,0,67,1,0,0,0,0,69,1,0,0,0,0,71,1, + 0,0,0,0,73,1,0,0,0,0,75,1,0,0,0,0,77,1,0,0,0,0,79,1,0,0,0,0,81,1, + 0,0,0,0,83,1,0,0,0,0,85,1,0,0,0,0,87,1,0,0,0,0,89,1,0,0,0,0,91,1, + 0,0,0,0,93,1,0,0,0,0,95,1,0,0,0,0,97,1,0,0,0,0,99,1,0,0,0,0,101, + 1,0,0,0,0,103,1,0,0,0,0,105,1,0,0,0,0,107,1,0,0,0,0,109,1,0,0,0, + 0,111,1,0,0,0,0,113,1,0,0,0,0,115,1,0,0,0,0,117,1,0,0,0,0,119,1, + 0,0,0,0,121,1,0,0,0,0,123,1,0,0,0,0,125,1,0,0,0,0,127,1,0,0,0,0, + 129,1,0,0,0,0,131,1,0,0,0,0,133,1,0,0,0,0,135,1,0,0,0,0,137,1,0, + 0,0,0,139,1,0,0,0,0,141,1,0,0,0,0,143,1,0,0,0,0,145,1,0,0,0,0,147, + 1,0,0,0,0,149,1,0,0,0,0,151,1,0,0,0,0,153,1,0,0,0,0,155,1,0,0,0, + 0,157,1,0,0,0,0,159,1,0,0,0,0,161,1,0,0,0,0,163,1,0,0,0,0,165,1, + 0,0,0,0,167,1,0,0,0,0,169,1,0,0,0,0,171,1,0,0,0,0,173,1,0,0,0,0, + 175,1,0,0,0,0,177,1,0,0,0,0,179,1,0,0,0,0,181,1,0,0,0,0,183,1,0, + 0,0,0,185,1,0,0,0,0,187,1,0,0,0,0,189,1,0,0,0,0,191,1,0,0,0,0,193, + 1,0,0,0,0,195,1,0,0,0,0,197,1,0,0,0,0,199,1,0,0,0,0,201,1,0,0,0, + 0,203,1,0,0,0,0,205,1,0,0,0,0,207,1,0,0,0,0,209,1,0,0,0,0,211,1, + 0,0,0,0,213,1,0,0,0,0,215,1,0,0,0,0,217,1,0,0,0,0,219,1,0,0,0,0, + 221,1,0,0,0,0,223,1,0,0,0,0,225,1,0,0,0,0,227,1,0,0,0,0,229,1,0, + 0,0,0,231,1,0,0,0,0,233,1,0,0,0,0,235,1,0,0,0,0,237,1,0,0,0,0,239, + 1,0,0,0,0,241,1,0,0,0,0,243,1,0,0,0,0,245,1,0,0,0,0,247,1,0,0,0, + 0,249,1,0,0,0,0,251,1,0,0,0,0,253,1,0,0,0,0,255,1,0,0,0,0,257,1, + 0,0,0,0,259,1,0,0,0,0,261,1,0,0,0,0,263,1,0,0,0,0,265,1,0,0,0,0, + 267,1,0,0,0,0,269,1,0,0,0,0,271,1,0,0,0,0,273,1,0,0,0,0,275,1,0, + 0,0,0,277,1,0,0,0,0,279,1,0,0,0,0,281,1,0,0,0,0,283,1,0,0,0,0,285, + 1,0,0,0,0,287,1,0,0,0,0,289,1,0,0,0,0,291,1,0,0,0,0,293,1,0,0,0, + 0,295,1,0,0,0,0,297,1,0,0,0,0,299,1,0,0,0,0,301,1,0,0,0,0,303,1, + 0,0,0,0,305,1,0,0,0,0,307,1,0,0,0,0,309,1,0,0,0,0,311,1,0,0,0,0, + 313,1,0,0,0,0,315,1,0,0,0,0,317,1,0,0,0,0,319,1,0,0,0,0,321,1,0, + 0,0,0,323,1,0,0,0,0,325,1,0,0,0,0,327,1,0,0,0,0,329,1,0,0,0,0,331, + 1,0,0,0,0,333,1,0,0,0,0,335,1,0,0,0,0,337,1,0,0,0,0,339,1,0,0,0, + 0,341,1,0,0,0,0,343,1,0,0,0,0,345,1,0,0,0,0,347,1,0,0,0,0,349,1, + 0,0,0,0,351,1,0,0,0,0,353,1,0,0,0,0,355,1,0,0,0,0,357,1,0,0,0,0, + 359,1,0,0,0,0,361,1,0,0,0,0,363,1,0,0,0,0,365,1,0,0,0,0,367,1,0, + 0,0,0,369,1,0,0,0,0,371,1,0,0,0,0,373,1,0,0,0,0,375,1,0,0,0,0,377, + 1,0,0,0,0,379,1,0,0,0,0,381,1,0,0,0,0,383,1,0,0,0,0,385,1,0,0,0, + 0,387,1,0,0,0,0,389,1,0,0,0,0,391,1,0,0,0,0,393,1,0,0,0,0,395,1, + 0,0,0,0,397,1,0,0,0,0,399,1,0,0,0,0,401,1,0,0,0,0,403,1,0,0,0,0, + 405,1,0,0,0,0,407,1,0,0,0,0,409,1,0,0,0,0,411,1,0,0,0,0,413,1,0, + 0,0,0,415,1,0,0,0,0,417,1,0,0,0,0,419,1,0,0,0,0,421,1,0,0,0,0,423, + 1,0,0,0,0,425,1,0,0,0,0,427,1,0,0,0,0,429,1,0,0,0,0,431,1,0,0,0, + 0,433,1,0,0,0,0,435,1,0,0,0,0,437,1,0,0,0,0,439,1,0,0,0,0,441,1, + 0,0,0,0,443,1,0,0,0,0,445,1,0,0,0,0,447,1,0,0,0,0,449,1,0,0,0,0, + 451,1,0,0,0,0,453,1,0,0,0,0,455,1,0,0,0,0,457,1,0,0,0,0,459,1,0, + 0,0,0,461,1,0,0,0,0,463,1,0,0,0,0,465,1,0,0,0,0,467,1,0,0,0,0,469, + 1,0,0,0,0,471,1,0,0,0,0,473,1,0,0,0,0,475,1,0,0,0,0,477,1,0,0,0, + 0,479,1,0,0,0,0,481,1,0,0,0,0,483,1,0,0,0,0,485,1,0,0,0,0,487,1, + 0,0,0,0,489,1,0,0,0,0,491,1,0,0,0,0,493,1,0,0,0,0,495,1,0,0,0,0, + 497,1,0,0,0,0,499,1,0,0,0,0,501,1,0,0,0,0,503,1,0,0,0,0,505,1,0, + 0,0,0,507,1,0,0,0,0,509,1,0,0,0,0,511,1,0,0,0,0,513,1,0,0,0,0,515, + 1,0,0,0,0,517,1,0,0,0,0,519,1,0,0,0,0,521,1,0,0,0,0,523,1,0,0,0, + 0,525,1,0,0,0,0,527,1,0,0,0,0,529,1,0,0,0,0,531,1,0,0,0,0,533,1, + 0,0,0,0,535,1,0,0,0,0,537,1,0,0,0,0,539,1,0,0,0,0,541,1,0,0,0,0, + 543,1,0,0,0,0,545,1,0,0,0,0,547,1,0,0,0,0,549,1,0,0,0,0,551,1,0, + 0,0,0,553,1,0,0,0,0,555,1,0,0,0,0,557,1,0,0,0,0,559,1,0,0,0,0,561, + 1,0,0,0,0,563,1,0,0,0,0,565,1,0,0,0,0,567,1,0,0,0,0,569,1,0,0,0, + 0,571,1,0,0,0,0,573,1,0,0,0,0,575,1,0,0,0,0,577,1,0,0,0,0,579,1, + 0,0,0,0,581,1,0,0,0,0,583,1,0,0,0,0,585,1,0,0,0,0,587,1,0,0,0,0, + 589,1,0,0,0,0,591,1,0,0,0,0,593,1,0,0,0,0,595,1,0,0,0,0,597,1,0, + 0,0,0,599,1,0,0,0,0,601,1,0,0,0,0,603,1,0,0,0,0,605,1,0,0,0,0,607, + 1,0,0,0,0,609,1,0,0,0,0,611,1,0,0,0,0,613,1,0,0,0,0,615,1,0,0,0, + 0,617,1,0,0,0,0,619,1,0,0,0,0,621,1,0,0,0,0,623,1,0,0,0,0,625,1, + 0,0,0,0,627,1,0,0,0,0,629,1,0,0,0,0,631,1,0,0,0,0,633,1,0,0,0,0, + 635,1,0,0,0,0,637,1,0,0,0,0,639,1,0,0,0,0,641,1,0,0,0,0,643,1,0, + 0,0,0,645,1,0,0,0,0,647,1,0,0,0,0,649,1,0,0,0,0,651,1,0,0,0,0,653, + 1,0,0,0,0,655,1,0,0,0,0,657,1,0,0,0,0,659,1,0,0,0,0,661,1,0,0,0, + 0,663,1,0,0,0,0,665,1,0,0,0,0,667,1,0,0,0,0,669,1,0,0,0,0,671,1, + 0,0,0,0,673,1,0,0,0,0,675,1,0,0,0,0,677,1,0,0,0,0,679,1,0,0,0,0, + 681,1,0,0,0,0,683,1,0,0,0,0,693,1,0,0,0,0,695,1,0,0,0,0,697,1,0, + 0,0,0,699,1,0,0,0,1,701,1,0,0,0,3,703,1,0,0,0,5,705,1,0,0,0,7,707, + 1,0,0,0,9,709,1,0,0,0,11,711,1,0,0,0,13,713,1,0,0,0,15,715,1,0,0, + 0,17,719,1,0,0,0,19,725,1,0,0,0,21,729,1,0,0,0,23,735,1,0,0,0,25, + 742,1,0,0,0,27,750,1,0,0,0,29,754,1,0,0,0,31,759,1,0,0,0,33,763, + 1,0,0,0,35,773,1,0,0,0,37,781,1,0,0,0,39,787,1,0,0,0,41,790,1,0, + 0,0,43,794,1,0,0,0,45,797,1,0,0,0,47,811,1,0,0,0,49,819,1,0,0,0, + 51,824,1,0,0,0,53,831,1,0,0,0,55,839,1,0,0,0,57,842,1,0,0,0,59,848, + 1,0,0,0,61,856,1,0,0,0,63,861,1,0,0,0,65,866,1,0,0,0,67,874,1,0, + 0,0,69,883,1,0,0,0,71,890,1,0,0,0,73,896,1,0,0,0,75,902,1,0,0,0, + 77,910,1,0,0,0,79,920,1,0,0,0,81,928,1,0,0,0,83,936,1,0,0,0,85,947, + 1,0,0,0,87,954,1,0,0,0,89,962,1,0,0,0,91,970,1,0,0,0,93,977,1,0, + 0,0,95,985,1,0,0,0,97,997,1,0,0,0,99,1005,1,0,0,0,101,1017,1,0,0, + 0,103,1028,1,0,0,0,105,1033,1,0,0,0,107,1040,1,0,0,0,109,1046,1, + 0,0,0,111,1051,1,0,0,0,113,1059,1,0,0,0,115,1072,1,0,0,0,117,1085, + 1,0,0,0,119,1103,1,0,0,0,121,1116,1,0,0,0,123,1120,1,0,0,0,125,1125, + 1,0,0,0,127,1135,1,0,0,0,129,1140,1,0,0,0,131,1149,1,0,0,0,133,1159, + 1,0,0,0,135,1167,1,0,0,0,137,1176,1,0,0,0,139,1189,1,0,0,0,141,1197, + 1,0,0,0,143,1205,1,0,0,0,145,1212,1,0,0,0,147,1222,1,0,0,0,149,1227, + 1,0,0,0,151,1236,1,0,0,0,153,1240,1,0,0,0,155,1252,1,0,0,0,157,1262, + 1,0,0,0,159,1271,1,0,0,0,161,1282,1,0,0,0,163,1286,1,0,0,0,165,1291, + 1,0,0,0,167,1296,1,0,0,0,169,1300,1,0,0,0,171,1307,1,0,0,0,173,1315, + 1,0,0,0,175,1322,1,0,0,0,177,1331,1,0,0,0,179,1339,1,0,0,0,181,1346, + 1,0,0,0,183,1354,1,0,0,0,185,1361,1,0,0,0,187,1370,1,0,0,0,189,1379, + 1,0,0,0,191,1387,1,0,0,0,193,1393,1,0,0,0,195,1399,1,0,0,0,197,1406, + 1,0,0,0,199,1413,1,0,0,0,201,1424,1,0,0,0,203,1430,1,0,0,0,205,1440, + 1,0,0,0,207,1444,1,0,0,0,209,1452,1,0,0,0,211,1459,1,0,0,0,213,1469, + 1,0,0,0,215,1474,1,0,0,0,217,1479,1,0,0,0,219,1488,1,0,0,0,221,1498, + 1,0,0,0,223,1508,1,0,0,0,225,1515,1,0,0,0,227,1521,1,0,0,0,229,1527, + 1,0,0,0,231,1536,1,0,0,0,233,1543,1,0,0,0,235,1548,1,0,0,0,237,1554, + 1,0,0,0,239,1557,1,0,0,0,241,1564,1,0,0,0,243,1571,1,0,0,0,245,1574, + 1,0,0,0,247,1582,1,0,0,0,249,1588,1,0,0,0,251,1596,1,0,0,0,253,1602, + 1,0,0,0,255,1609,1,0,0,0,257,1621,1,0,0,0,259,1628,1,0,0,0,261,1638, + 1,0,0,0,263,1647,1,0,0,0,265,1652,1,0,0,0,267,1655,1,0,0,0,269,1661, + 1,0,0,0,271,1666,1,0,0,0,273,1671,1,0,0,0,275,1676,1,0,0,0,277,1684, + 1,0,0,0,279,1689,1,0,0,0,281,1697,1,0,0,0,283,1702,1,0,0,0,285,1707, + 1,0,0,0,287,1713,1,0,0,0,289,1719,1,0,0,0,291,1725,1,0,0,0,293,1730, + 1,0,0,0,295,1735,1,0,0,0,297,1741,1,0,0,0,299,1750,1,0,0,0,301,1755, + 1,0,0,0,303,1761,1,0,0,0,305,1769,1,0,0,0,307,1775,1,0,0,0,309,1779, + 1,0,0,0,311,1787,1,0,0,0,313,1793,1,0,0,0,315,1805,1,0,0,0,317,1818, + 1,0,0,0,319,1830,1,0,0,0,321,1843,1,0,0,0,323,1850,1,0,0,0,325,1858, + 1,0,0,0,327,1864,1,0,0,0,329,1871,1,0,0,0,331,1876,1,0,0,0,333,1886, + 1,0,0,0,335,1897,1,0,0,0,337,1908,1,0,0,0,339,1920,1,0,0,0,341,1928, + 1,0,0,0,343,1935,1,0,0,0,345,1937,1,0,0,0,347,1942,1,0,0,0,349,1948, + 1,0,0,0,351,1951,1,0,0,0,353,1958,1,0,0,0,355,1961,1,0,0,0,357,1966, + 1,0,0,0,359,1973,1,0,0,0,361,1981,1,0,0,0,363,1984,1,0,0,0,365,1990, + 1,0,0,0,367,1994,1,0,0,0,369,2000,1,0,0,0,371,2013,1,0,0,0,373,2018, + 1,0,0,0,375,2027,1,0,0,0,377,2035,1,0,0,0,379,2045,1,0,0,0,381,2055, + 1,0,0,0,383,2067,1,0,0,0,385,2078,1,0,0,0,387,2094,1,0,0,0,389,2110, + 1,0,0,0,391,2118,1,0,0,0,393,2124,1,0,0,0,395,2132,1,0,0,0,397,2141, + 1,0,0,0,399,2151,1,0,0,0,401,2159,1,0,0,0,403,2170,1,0,0,0,405,2181, + 1,0,0,0,407,2187,1,0,0,0,409,2195,1,0,0,0,411,2201,1,0,0,0,413,2207, + 1,0,0,0,415,2220,1,0,0,0,417,2233,1,0,0,0,419,2241,1,0,0,0,421,2248, + 1,0,0,0,423,2259,1,0,0,0,425,2267,1,0,0,0,427,2274,1,0,0,0,429,2281, + 1,0,0,0,431,2292,1,0,0,0,433,2300,1,0,0,0,435,2306,1,0,0,0,437,2314, + 1,0,0,0,439,2323,1,0,0,0,441,2330,1,0,0,0,443,2347,1,0,0,0,445,2349, + 1,0,0,0,447,2354,1,0,0,0,449,2360,1,0,0,0,451,2369,1,0,0,0,453,2376, + 1,0,0,0,455,2380,1,0,0,0,457,2385,1,0,0,0,459,2392,1,0,0,0,461,2400, + 1,0,0,0,463,2407,1,0,0,0,465,2415,1,0,0,0,467,2422,1,0,0,0,469,2427, + 1,0,0,0,471,2437,1,0,0,0,473,2443,1,0,0,0,475,2459,1,0,0,0,477,2472, + 1,0,0,0,479,2476,1,0,0,0,481,2482,1,0,0,0,483,2487,1,0,0,0,485,2492, + 1,0,0,0,487,2499,1,0,0,0,489,2504,1,0,0,0,491,2509,1,0,0,0,493,2516, + 1,0,0,0,495,2523,1,0,0,0,497,2529,1,0,0,0,499,2540,1,0,0,0,501,2547, + 1,0,0,0,503,2556,1,0,0,0,505,2563,1,0,0,0,507,2570,1,0,0,0,509,2580, + 1,0,0,0,511,2585,1,0,0,0,513,2597,1,0,0,0,515,2612,1,0,0,0,517,2618, + 1,0,0,0,519,2625,1,0,0,0,521,2637,1,0,0,0,523,2644,1,0,0,0,525,2671, + 1,0,0,0,527,2673,1,0,0,0,529,2684,1,0,0,0,531,2689,1,0,0,0,533,2694, + 1,0,0,0,535,2704,1,0,0,0,537,2717,1,0,0,0,539,2731,1,0,0,0,541,2734, + 1,0,0,0,543,2740,1,0,0,0,545,2749,1,0,0,0,547,2761,1,0,0,0,549,2774, + 1,0,0,0,551,2784,1,0,0,0,553,2789,1,0,0,0,555,2794,1,0,0,0,557,2803, + 1,0,0,0,559,2812,1,0,0,0,561,2817,1,0,0,0,563,2827,1,0,0,0,565,2837, + 1,0,0,0,567,2845,1,0,0,0,569,2851,1,0,0,0,571,2858,1,0,0,0,573,2866, + 1,0,0,0,575,2873,1,0,0,0,577,2881,1,0,0,0,579,2887,1,0,0,0,581,2894, + 1,0,0,0,583,2898,1,0,0,0,585,2903,1,0,0,0,587,2909,1,0,0,0,589,2916, + 1,0,0,0,591,2924,1,0,0,0,593,2929,1,0,0,0,595,2935,1,0,0,0,597,2940, + 1,0,0,0,599,2946,1,0,0,0,601,2951,1,0,0,0,603,2957,1,0,0,0,605,2964, + 1,0,0,0,607,2969,1,0,0,0,609,2976,1,0,0,0,611,2981,1,0,0,0,613,2987, + 1,0,0,0,615,2995,1,0,0,0,617,2997,1,0,0,0,619,3001,1,0,0,0,621,3004, + 1,0,0,0,623,3007,1,0,0,0,625,3013,1,0,0,0,627,3015,1,0,0,0,629,3021, + 1,0,0,0,631,3023,1,0,0,0,633,3025,1,0,0,0,635,3027,1,0,0,0,637,3029, + 1,0,0,0,639,3031,1,0,0,0,641,3033,1,0,0,0,643,3035,1,0,0,0,645,3037, + 1,0,0,0,647,3039,1,0,0,0,649,3042,1,0,0,0,651,3044,1,0,0,0,653,3046, + 1,0,0,0,655,3049,1,0,0,0,657,3053,1,0,0,0,659,3086,1,0,0,0,661,3088, + 1,0,0,0,663,3100,1,0,0,0,665,3107,1,0,0,0,667,3114,1,0,0,0,669,3121, + 1,0,0,0,671,3135,1,0,0,0,673,3137,1,0,0,0,675,3155,1,0,0,0,677,3173, + 1,0,0,0,679,3193,1,0,0,0,681,3198,1,0,0,0,683,3202,1,0,0,0,685,3231, + 1,0,0,0,687,3233,1,0,0,0,689,3242,1,0,0,0,691,3244,1,0,0,0,693,3246, + 1,0,0,0,695,3265,1,0,0,0,697,3284,1,0,0,0,699,3290,1,0,0,0,701,702, + 5,59,0,0,702,2,1,0,0,0,703,704,5,40,0,0,704,4,1,0,0,0,705,706,5, + 41,0,0,706,6,1,0,0,0,707,708,5,44,0,0,708,8,1,0,0,0,709,710,5,46, + 0,0,710,10,1,0,0,0,711,712,5,91,0,0,712,12,1,0,0,0,713,714,5,93, + 0,0,714,14,1,0,0,0,715,716,5,65,0,0,716,717,5,68,0,0,717,718,5,68, + 0,0,718,16,1,0,0,0,719,720,5,65,0,0,720,721,5,70,0,0,721,722,5,84, + 0,0,722,723,5,69,0,0,723,724,5,82,0,0,724,18,1,0,0,0,725,726,5,65, + 0,0,726,727,5,76,0,0,727,728,5,76,0,0,728,20,1,0,0,0,729,730,5,65, + 0,0,730,731,5,76,0,0,731,732,5,84,0,0,732,733,5,69,0,0,733,734,5, + 82,0,0,734,22,1,0,0,0,735,736,5,65,0,0,736,737,5,76,0,0,737,738, + 5,87,0,0,738,739,5,65,0,0,739,740,5,89,0,0,740,741,5,83,0,0,741, + 24,1,0,0,0,742,743,5,65,0,0,743,744,5,78,0,0,744,745,5,65,0,0,745, + 746,5,76,0,0,746,747,5,89,0,0,747,748,5,90,0,0,748,749,5,69,0,0, + 749,26,1,0,0,0,750,751,5,65,0,0,751,752,5,78,0,0,752,753,5,68,0, + 0,753,28,1,0,0,0,754,755,5,65,0,0,755,756,5,78,0,0,756,757,5,84, + 0,0,757,758,5,73,0,0,758,30,1,0,0,0,759,760,5,65,0,0,760,761,5,78, + 0,0,761,762,5,89,0,0,762,32,1,0,0,0,763,764,5,65,0,0,764,765,5,78, + 0,0,765,766,5,89,0,0,766,767,5,95,0,0,767,768,5,86,0,0,768,769,5, + 65,0,0,769,770,5,76,0,0,770,771,5,85,0,0,771,772,5,69,0,0,772,34, + 1,0,0,0,773,774,5,65,0,0,774,775,5,82,0,0,775,776,5,67,0,0,776,777, + 5,72,0,0,777,778,5,73,0,0,778,779,5,86,0,0,779,780,5,69,0,0,780, + 36,1,0,0,0,781,782,5,65,0,0,782,783,5,82,0,0,783,784,5,82,0,0,784, + 785,5,65,0,0,785,786,5,89,0,0,786,38,1,0,0,0,787,788,5,65,0,0,788, + 789,5,83,0,0,789,40,1,0,0,0,790,791,5,65,0,0,791,792,5,83,0,0,792, + 793,5,67,0,0,793,42,1,0,0,0,794,795,5,65,0,0,795,796,5,84,0,0,796, + 44,1,0,0,0,797,798,5,65,0,0,798,799,5,85,0,0,799,800,5,84,0,0,800, + 801,5,72,0,0,801,802,5,79,0,0,802,803,5,82,0,0,803,804,5,73,0,0, + 804,805,5,90,0,0,805,806,5,65,0,0,806,807,5,84,0,0,807,808,5,73, + 0,0,808,809,5,79,0,0,809,810,5,78,0,0,810,46,1,0,0,0,811,812,5,66, + 0,0,812,813,5,69,0,0,813,814,5,84,0,0,814,815,5,87,0,0,815,816,5, + 69,0,0,816,817,5,69,0,0,817,818,5,78,0,0,818,48,1,0,0,0,819,820, + 5,66,0,0,820,821,5,79,0,0,821,822,5,84,0,0,822,823,5,72,0,0,823, + 50,1,0,0,0,824,825,5,66,0,0,825,826,5,85,0,0,826,827,5,67,0,0,827, + 828,5,75,0,0,828,829,5,69,0,0,829,830,5,84,0,0,830,52,1,0,0,0,831, + 832,5,66,0,0,832,833,5,85,0,0,833,834,5,67,0,0,834,835,5,75,0,0, + 835,836,5,69,0,0,836,837,5,84,0,0,837,838,5,83,0,0,838,54,1,0,0, + 0,839,840,5,66,0,0,840,841,5,89,0,0,841,56,1,0,0,0,842,843,5,67, + 0,0,843,844,5,65,0,0,844,845,5,67,0,0,845,846,5,72,0,0,846,847,5, + 69,0,0,847,58,1,0,0,0,848,849,5,67,0,0,849,850,5,65,0,0,850,851, + 5,83,0,0,851,852,5,67,0,0,852,853,5,65,0,0,853,854,5,68,0,0,854, + 855,5,69,0,0,855,60,1,0,0,0,856,857,5,67,0,0,857,858,5,65,0,0,858, + 859,5,83,0,0,859,860,5,69,0,0,860,62,1,0,0,0,861,862,5,67,0,0,862, + 863,5,65,0,0,863,864,5,83,0,0,864,865,5,84,0,0,865,64,1,0,0,0,866, + 867,5,67,0,0,867,868,5,65,0,0,868,869,5,84,0,0,869,870,5,65,0,0, + 870,871,5,76,0,0,871,872,5,79,0,0,872,873,5,71,0,0,873,66,1,0,0, + 0,874,875,5,67,0,0,875,876,5,65,0,0,876,877,5,84,0,0,877,878,5,65, + 0,0,878,879,5,76,0,0,879,880,5,79,0,0,880,881,5,71,0,0,881,882,5, + 83,0,0,882,68,1,0,0,0,883,884,5,67,0,0,884,885,5,72,0,0,885,886, + 5,65,0,0,886,887,5,78,0,0,887,888,5,71,0,0,888,889,5,69,0,0,889, + 70,1,0,0,0,890,891,5,67,0,0,891,892,5,72,0,0,892,893,5,69,0,0,893, + 894,5,67,0,0,894,895,5,75,0,0,895,72,1,0,0,0,896,897,5,67,0,0,897, + 898,5,76,0,0,898,899,5,69,0,0,899,900,5,65,0,0,900,901,5,82,0,0, + 901,74,1,0,0,0,902,903,5,67,0,0,903,904,5,76,0,0,904,905,5,85,0, + 0,905,906,5,83,0,0,906,907,5,84,0,0,907,908,5,69,0,0,908,909,5,82, + 0,0,909,76,1,0,0,0,910,911,5,67,0,0,911,912,5,76,0,0,912,913,5,85, + 0,0,913,914,5,83,0,0,914,915,5,84,0,0,915,916,5,69,0,0,916,917,5, + 82,0,0,917,918,5,69,0,0,918,919,5,68,0,0,919,78,1,0,0,0,920,921, + 5,67,0,0,921,922,5,79,0,0,922,923,5,68,0,0,923,924,5,69,0,0,924, + 925,5,71,0,0,925,926,5,69,0,0,926,927,5,78,0,0,927,80,1,0,0,0,928, + 929,5,67,0,0,929,930,5,79,0,0,930,931,5,76,0,0,931,932,5,76,0,0, + 932,933,5,65,0,0,933,934,5,84,0,0,934,935,5,69,0,0,935,82,1,0,0, + 0,936,937,5,67,0,0,937,938,5,79,0,0,938,939,5,76,0,0,939,940,5,76, + 0,0,940,941,5,69,0,0,941,942,5,67,0,0,942,943,5,84,0,0,943,944,5, + 73,0,0,944,945,5,79,0,0,945,946,5,78,0,0,946,84,1,0,0,0,947,948, + 5,67,0,0,948,949,5,79,0,0,949,950,5,76,0,0,950,951,5,85,0,0,951, + 952,5,77,0,0,952,953,5,78,0,0,953,86,1,0,0,0,954,955,5,67,0,0,955, + 956,5,79,0,0,956,957,5,76,0,0,957,958,5,85,0,0,958,959,5,77,0,0, + 959,960,5,78,0,0,960,961,5,83,0,0,961,88,1,0,0,0,962,963,5,67,0, + 0,963,964,5,79,0,0,964,965,5,77,0,0,965,966,5,77,0,0,966,967,5,69, + 0,0,967,968,5,78,0,0,968,969,5,84,0,0,969,90,1,0,0,0,970,971,5,67, + 0,0,971,972,5,79,0,0,972,973,5,77,0,0,973,974,5,77,0,0,974,975,5, + 73,0,0,975,976,5,84,0,0,976,92,1,0,0,0,977,978,5,67,0,0,978,979, + 5,79,0,0,979,980,5,77,0,0,980,981,5,80,0,0,981,982,5,65,0,0,982, + 983,5,67,0,0,983,984,5,84,0,0,984,94,1,0,0,0,985,986,5,67,0,0,986, + 987,5,79,0,0,987,988,5,77,0,0,988,989,5,80,0,0,989,990,5,65,0,0, + 990,991,5,67,0,0,991,992,5,84,0,0,992,993,5,73,0,0,993,994,5,79, + 0,0,994,995,5,78,0,0,995,996,5,83,0,0,996,96,1,0,0,0,997,998,5,67, + 0,0,998,999,5,79,0,0,999,1000,5,77,0,0,1000,1001,5,80,0,0,1001,1002, + 5,85,0,0,1002,1003,5,84,0,0,1003,1004,5,69,0,0,1004,98,1,0,0,0,1005, + 1006,5,67,0,0,1006,1007,5,79,0,0,1007,1008,5,78,0,0,1008,1009,5, + 67,0,0,1009,1010,5,65,0,0,1010,1011,5,84,0,0,1011,1012,5,69,0,0, + 1012,1013,5,78,0,0,1013,1014,5,65,0,0,1014,1015,5,84,0,0,1015,1016, + 5,69,0,0,1016,100,1,0,0,0,1017,1018,5,67,0,0,1018,1019,5,79,0,0, + 1019,1020,5,78,0,0,1020,1021,5,83,0,0,1021,1022,5,84,0,0,1022,1023, + 5,82,0,0,1023,1024,5,65,0,0,1024,1025,5,73,0,0,1025,1026,5,78,0, + 0,1026,1027,5,84,0,0,1027,102,1,0,0,0,1028,1029,5,67,0,0,1029,1030, + 5,79,0,0,1030,1031,5,83,0,0,1031,1032,5,84,0,0,1032,104,1,0,0,0, + 1033,1034,5,67,0,0,1034,1035,5,82,0,0,1035,1036,5,69,0,0,1036,1037, + 5,65,0,0,1037,1038,5,84,0,0,1038,1039,5,69,0,0,1039,106,1,0,0,0, + 1040,1041,5,67,0,0,1041,1042,5,82,0,0,1042,1043,5,79,0,0,1043,1044, + 5,83,0,0,1044,1045,5,83,0,0,1045,108,1,0,0,0,1046,1047,5,67,0,0, + 1047,1048,5,85,0,0,1048,1049,5,66,0,0,1049,1050,5,69,0,0,1050,110, + 1,0,0,0,1051,1052,5,67,0,0,1052,1053,5,85,0,0,1053,1054,5,82,0,0, + 1054,1055,5,82,0,0,1055,1056,5,69,0,0,1056,1057,5,78,0,0,1057,1058, + 5,84,0,0,1058,112,1,0,0,0,1059,1060,5,67,0,0,1060,1061,5,85,0,0, + 1061,1062,5,82,0,0,1062,1063,5,82,0,0,1063,1064,5,69,0,0,1064,1065, + 5,78,0,0,1065,1066,5,84,0,0,1066,1067,5,95,0,0,1067,1068,5,68,0, + 0,1068,1069,5,65,0,0,1069,1070,5,84,0,0,1070,1071,5,69,0,0,1071, + 114,1,0,0,0,1072,1073,5,67,0,0,1073,1074,5,85,0,0,1074,1075,5,82, + 0,0,1075,1076,5,82,0,0,1076,1077,5,69,0,0,1077,1078,5,78,0,0,1078, + 1079,5,84,0,0,1079,1080,5,95,0,0,1080,1081,5,84,0,0,1081,1082,5, + 73,0,0,1082,1083,5,77,0,0,1083,1084,5,69,0,0,1084,116,1,0,0,0,1085, + 1086,5,67,0,0,1086,1087,5,85,0,0,1087,1088,5,82,0,0,1088,1089,5, + 82,0,0,1089,1090,5,69,0,0,1090,1091,5,78,0,0,1091,1092,5,84,0,0, + 1092,1093,5,95,0,0,1093,1094,5,84,0,0,1094,1095,5,73,0,0,1095,1096, + 5,77,0,0,1096,1097,5,69,0,0,1097,1098,5,83,0,0,1098,1099,5,84,0, + 0,1099,1100,5,65,0,0,1100,1101,5,77,0,0,1101,1102,5,80,0,0,1102, + 118,1,0,0,0,1103,1104,5,67,0,0,1104,1105,5,85,0,0,1105,1106,5,82, + 0,0,1106,1107,5,82,0,0,1107,1108,5,69,0,0,1108,1109,5,78,0,0,1109, + 1110,5,84,0,0,1110,1111,5,95,0,0,1111,1112,5,85,0,0,1112,1113,5, + 83,0,0,1113,1114,5,69,0,0,1114,1115,5,82,0,0,1115,120,1,0,0,0,1116, + 1117,5,68,0,0,1117,1118,5,65,0,0,1118,1119,5,89,0,0,1119,122,1,0, + 0,0,1120,1121,5,68,0,0,1121,1122,5,65,0,0,1122,1123,5,89,0,0,1123, + 1124,5,83,0,0,1124,124,1,0,0,0,1125,1126,5,68,0,0,1126,1127,5,65, + 0,0,1127,1128,5,89,0,0,1128,1129,5,79,0,0,1129,1130,5,70,0,0,1130, + 1131,5,89,0,0,1131,1132,5,69,0,0,1132,1133,5,65,0,0,1133,1134,5, + 82,0,0,1134,126,1,0,0,0,1135,1136,5,68,0,0,1136,1137,5,65,0,0,1137, + 1138,5,84,0,0,1138,1139,5,65,0,0,1139,128,1,0,0,0,1140,1141,5,68, + 0,0,1141,1142,5,65,0,0,1142,1143,5,84,0,0,1143,1144,5,65,0,0,1144, + 1145,5,66,0,0,1145,1146,5,65,0,0,1146,1147,5,83,0,0,1147,1148,5, + 69,0,0,1148,130,1,0,0,0,1149,1150,5,68,0,0,1150,1151,5,65,0,0,1151, + 1152,5,84,0,0,1152,1153,5,65,0,0,1153,1154,5,66,0,0,1154,1155,5, + 65,0,0,1155,1156,5,83,0,0,1156,1157,5,69,0,0,1157,1158,5,83,0,0, + 1158,132,1,0,0,0,1159,1160,5,68,0,0,1160,1161,5,65,0,0,1161,1162, + 5,84,0,0,1162,1163,5,69,0,0,1163,1164,5,65,0,0,1164,1165,5,68,0, + 0,1165,1166,5,68,0,0,1166,134,1,0,0,0,1167,1168,5,68,0,0,1168,1169, + 5,65,0,0,1169,1170,5,84,0,0,1170,1171,5,69,0,0,1171,1172,5,68,0, + 0,1172,1173,5,73,0,0,1173,1174,5,70,0,0,1174,1175,5,70,0,0,1175, + 136,1,0,0,0,1176,1177,5,68,0,0,1177,1178,5,66,0,0,1178,1179,5,80, + 0,0,1179,1180,5,82,0,0,1180,1181,5,79,0,0,1181,1182,5,80,0,0,1182, + 1183,5,69,0,0,1183,1184,5,82,0,0,1184,1185,5,84,0,0,1185,1186,5, + 73,0,0,1186,1187,5,69,0,0,1187,1188,5,83,0,0,1188,138,1,0,0,0,1189, + 1190,5,68,0,0,1190,1191,5,69,0,0,1191,1192,5,70,0,0,1192,1193,5, + 65,0,0,1193,1194,5,85,0,0,1194,1195,5,76,0,0,1195,1196,5,84,0,0, + 1196,140,1,0,0,0,1197,1198,5,68,0,0,1198,1199,5,69,0,0,1199,1200, + 5,70,0,0,1200,1201,5,73,0,0,1201,1202,5,78,0,0,1202,1203,5,69,0, + 0,1203,1204,5,68,0,0,1204,142,1,0,0,0,1205,1206,5,68,0,0,1206,1207, + 5,69,0,0,1207,1208,5,76,0,0,1208,1209,5,69,0,0,1209,1210,5,84,0, + 0,1210,1211,5,69,0,0,1211,144,1,0,0,0,1212,1213,5,68,0,0,1213,1214, + 5,69,0,0,1214,1215,5,76,0,0,1215,1216,5,73,0,0,1216,1217,5,77,0, + 0,1217,1218,5,73,0,0,1218,1219,5,84,0,0,1219,1220,5,69,0,0,1220, + 1221,5,68,0,0,1221,146,1,0,0,0,1222,1223,5,68,0,0,1223,1224,5,69, + 0,0,1224,1225,5,83,0,0,1225,1226,5,67,0,0,1226,148,1,0,0,0,1227, + 1228,5,68,0,0,1228,1229,5,69,0,0,1229,1230,5,83,0,0,1230,1231,5, + 67,0,0,1231,1232,5,82,0,0,1232,1233,5,73,0,0,1233,1234,5,66,0,0, + 1234,1235,5,69,0,0,1235,150,1,0,0,0,1236,1237,5,68,0,0,1237,1238, + 5,70,0,0,1238,1239,5,83,0,0,1239,152,1,0,0,0,1240,1241,5,68,0,0, + 1241,1242,5,73,0,0,1242,1243,5,82,0,0,1243,1244,5,69,0,0,1244,1245, + 5,67,0,0,1245,1246,5,84,0,0,1246,1247,5,79,0,0,1247,1248,5,82,0, + 0,1248,1249,5,73,0,0,1249,1250,5,69,0,0,1250,1251,5,83,0,0,1251, + 154,1,0,0,0,1252,1253,5,68,0,0,1253,1254,5,73,0,0,1254,1255,5,82, + 0,0,1255,1256,5,69,0,0,1256,1257,5,67,0,0,1257,1258,5,84,0,0,1258, + 1259,5,79,0,0,1259,1260,5,82,0,0,1260,1261,5,89,0,0,1261,156,1,0, + 0,0,1262,1263,5,68,0,0,1263,1264,5,73,0,0,1264,1265,5,83,0,0,1265, + 1266,5,84,0,0,1266,1267,5,73,0,0,1267,1268,5,78,0,0,1268,1269,5, + 67,0,0,1269,1270,5,84,0,0,1270,158,1,0,0,0,1271,1272,5,68,0,0,1272, + 1273,5,73,0,0,1273,1274,5,83,0,0,1274,1275,5,84,0,0,1275,1276,5, + 82,0,0,1276,1277,5,73,0,0,1277,1278,5,66,0,0,1278,1279,5,85,0,0, + 1279,1280,5,84,0,0,1280,1281,5,69,0,0,1281,160,1,0,0,0,1282,1283, + 5,68,0,0,1283,1284,5,73,0,0,1284,1285,5,86,0,0,1285,162,1,0,0,0, + 1286,1287,5,68,0,0,1287,1288,5,82,0,0,1288,1289,5,79,0,0,1289,1290, + 5,80,0,0,1290,164,1,0,0,0,1291,1292,5,69,0,0,1292,1293,5,76,0,0, + 1293,1294,5,83,0,0,1294,1295,5,69,0,0,1295,166,1,0,0,0,1296,1297, + 5,69,0,0,1297,1298,5,78,0,0,1298,1299,5,68,0,0,1299,168,1,0,0,0, + 1300,1301,5,69,0,0,1301,1302,5,83,0,0,1302,1303,5,67,0,0,1303,1304, + 5,65,0,0,1304,1305,5,80,0,0,1305,1306,5,69,0,0,1306,170,1,0,0,0, + 1307,1308,5,69,0,0,1308,1309,5,83,0,0,1309,1310,5,67,0,0,1310,1311, + 5,65,0,0,1311,1312,5,80,0,0,1312,1313,5,69,0,0,1313,1314,5,68,0, + 0,1314,172,1,0,0,0,1315,1316,5,69,0,0,1316,1317,5,88,0,0,1317,1318, + 5,67,0,0,1318,1319,5,69,0,0,1319,1320,5,80,0,0,1320,1321,5,84,0, + 0,1321,174,1,0,0,0,1322,1323,5,69,0,0,1323,1324,5,88,0,0,1324,1325, + 5,67,0,0,1325,1326,5,72,0,0,1326,1327,5,65,0,0,1327,1328,5,78,0, + 0,1328,1329,5,71,0,0,1329,1330,5,69,0,0,1330,176,1,0,0,0,1331,1332, + 5,69,0,0,1332,1333,5,88,0,0,1333,1334,5,67,0,0,1334,1335,5,76,0, + 0,1335,1336,5,85,0,0,1336,1337,5,68,0,0,1337,1338,5,69,0,0,1338, + 178,1,0,0,0,1339,1340,5,69,0,0,1340,1341,5,88,0,0,1341,1342,5,73, + 0,0,1342,1343,5,83,0,0,1343,1344,5,84,0,0,1344,1345,5,83,0,0,1345, + 180,1,0,0,0,1346,1347,5,69,0,0,1347,1348,5,88,0,0,1348,1349,5,80, + 0,0,1349,1350,5,76,0,0,1350,1351,5,65,0,0,1351,1352,5,73,0,0,1352, + 1353,5,78,0,0,1353,182,1,0,0,0,1354,1355,5,69,0,0,1355,1356,5,88, + 0,0,1356,1357,5,80,0,0,1357,1358,5,79,0,0,1358,1359,5,82,0,0,1359, + 1360,5,84,0,0,1360,184,1,0,0,0,1361,1362,5,69,0,0,1362,1363,5,88, + 0,0,1363,1364,5,84,0,0,1364,1365,5,69,0,0,1365,1366,5,78,0,0,1366, + 1367,5,68,0,0,1367,1368,5,69,0,0,1368,1369,5,68,0,0,1369,186,1,0, + 0,0,1370,1371,5,69,0,0,1371,1372,5,88,0,0,1372,1373,5,84,0,0,1373, + 1374,5,69,0,0,1374,1375,5,82,0,0,1375,1376,5,78,0,0,1376,1377,5, + 65,0,0,1377,1378,5,76,0,0,1378,188,1,0,0,0,1379,1380,5,69,0,0,1380, + 1381,5,88,0,0,1381,1382,5,84,0,0,1382,1383,5,82,0,0,1383,1384,5, + 65,0,0,1384,1385,5,67,0,0,1385,1386,5,84,0,0,1386,190,1,0,0,0,1387, + 1388,5,70,0,0,1388,1389,5,65,0,0,1389,1390,5,76,0,0,1390,1391,5, + 83,0,0,1391,1392,5,69,0,0,1392,192,1,0,0,0,1393,1394,5,70,0,0,1394, + 1395,5,69,0,0,1395,1396,5,84,0,0,1396,1397,5,67,0,0,1397,1398,5, + 72,0,0,1398,194,1,0,0,0,1399,1400,5,70,0,0,1400,1401,5,73,0,0,1401, + 1402,5,69,0,0,1402,1403,5,76,0,0,1403,1404,5,68,0,0,1404,1405,5, + 83,0,0,1405,196,1,0,0,0,1406,1407,5,70,0,0,1407,1408,5,73,0,0,1408, + 1409,5,76,0,0,1409,1410,5,84,0,0,1410,1411,5,69,0,0,1411,1412,5, + 82,0,0,1412,198,1,0,0,0,1413,1414,5,70,0,0,1414,1415,5,73,0,0,1415, + 1416,5,76,0,0,1416,1417,5,69,0,0,1417,1418,5,70,0,0,1418,1419,5, + 79,0,0,1419,1420,5,82,0,0,1420,1421,5,77,0,0,1421,1422,5,65,0,0, + 1422,1423,5,84,0,0,1423,200,1,0,0,0,1424,1425,5,70,0,0,1425,1426, + 5,73,0,0,1426,1427,5,82,0,0,1427,1428,5,83,0,0,1428,1429,5,84,0, + 0,1429,202,1,0,0,0,1430,1431,5,70,0,0,1431,1432,5,79,0,0,1432,1433, + 5,76,0,0,1433,1434,5,76,0,0,1434,1435,5,79,0,0,1435,1436,5,87,0, + 0,1436,1437,5,73,0,0,1437,1438,5,78,0,0,1438,1439,5,71,0,0,1439, + 204,1,0,0,0,1440,1441,5,70,0,0,1441,1442,5,79,0,0,1442,1443,5,82, + 0,0,1443,206,1,0,0,0,1444,1445,5,70,0,0,1445,1446,5,79,0,0,1446, + 1447,5,82,0,0,1447,1448,5,69,0,0,1448,1449,5,73,0,0,1449,1450,5, + 71,0,0,1450,1451,5,78,0,0,1451,208,1,0,0,0,1452,1453,5,70,0,0,1453, + 1454,5,79,0,0,1454,1455,5,82,0,0,1455,1456,5,77,0,0,1456,1457,5, + 65,0,0,1457,1458,5,84,0,0,1458,210,1,0,0,0,1459,1460,5,70,0,0,1460, + 1461,5,79,0,0,1461,1462,5,82,0,0,1462,1463,5,77,0,0,1463,1464,5, + 65,0,0,1464,1465,5,84,0,0,1465,1466,5,84,0,0,1466,1467,5,69,0,0, + 1467,1468,5,68,0,0,1468,212,1,0,0,0,1469,1470,5,70,0,0,1470,1471, + 5,82,0,0,1471,1472,5,79,0,0,1472,1473,5,77,0,0,1473,214,1,0,0,0, + 1474,1475,5,70,0,0,1475,1476,5,85,0,0,1476,1477,5,76,0,0,1477,1478, + 5,76,0,0,1478,216,1,0,0,0,1479,1480,5,70,0,0,1480,1481,5,85,0,0, + 1481,1482,5,78,0,0,1482,1483,5,67,0,0,1483,1484,5,84,0,0,1484,1485, + 5,73,0,0,1485,1486,5,79,0,0,1486,1487,5,78,0,0,1487,218,1,0,0,0, + 1488,1489,5,70,0,0,1489,1490,5,85,0,0,1490,1491,5,78,0,0,1491,1492, + 5,67,0,0,1492,1493,5,84,0,0,1493,1494,5,73,0,0,1494,1495,5,79,0, + 0,1495,1496,5,78,0,0,1496,1497,5,83,0,0,1497,220,1,0,0,0,1498,1499, + 5,71,0,0,1499,1500,5,69,0,0,1500,1501,5,78,0,0,1501,1502,5,69,0, + 0,1502,1503,5,82,0,0,1503,1504,5,65,0,0,1504,1505,5,84,0,0,1505, + 1506,5,69,0,0,1506,1507,5,68,0,0,1507,222,1,0,0,0,1508,1509,5,71, + 0,0,1509,1510,5,76,0,0,1510,1511,5,79,0,0,1511,1512,5,66,0,0,1512, + 1513,5,65,0,0,1513,1514,5,76,0,0,1514,224,1,0,0,0,1515,1516,5,71, + 0,0,1516,1517,5,82,0,0,1517,1518,5,65,0,0,1518,1519,5,78,0,0,1519, + 1520,5,84,0,0,1520,226,1,0,0,0,1521,1522,5,71,0,0,1522,1523,5,82, + 0,0,1523,1524,5,79,0,0,1524,1525,5,85,0,0,1525,1526,5,80,0,0,1526, + 228,1,0,0,0,1527,1528,5,71,0,0,1528,1529,5,82,0,0,1529,1530,5,79, + 0,0,1530,1531,5,85,0,0,1531,1532,5,80,0,0,1532,1533,5,73,0,0,1533, + 1534,5,78,0,0,1534,1535,5,71,0,0,1535,230,1,0,0,0,1536,1537,5,72, + 0,0,1537,1538,5,65,0,0,1538,1539,5,86,0,0,1539,1540,5,73,0,0,1540, + 1541,5,78,0,0,1541,1542,5,71,0,0,1542,232,1,0,0,0,1543,1544,5,72, + 0,0,1544,1545,5,79,0,0,1545,1546,5,85,0,0,1546,1547,5,82,0,0,1547, + 234,1,0,0,0,1548,1549,5,72,0,0,1549,1550,5,79,0,0,1550,1551,5,85, + 0,0,1551,1552,5,82,0,0,1552,1553,5,83,0,0,1553,236,1,0,0,0,1554, + 1555,5,73,0,0,1555,1556,5,70,0,0,1556,238,1,0,0,0,1557,1558,5,73, + 0,0,1558,1559,5,71,0,0,1559,1560,5,78,0,0,1560,1561,5,79,0,0,1561, + 1562,5,82,0,0,1562,1563,5,69,0,0,1563,240,1,0,0,0,1564,1565,5,73, + 0,0,1565,1566,5,77,0,0,1566,1567,5,80,0,0,1567,1568,5,79,0,0,1568, + 1569,5,82,0,0,1569,1570,5,84,0,0,1570,242,1,0,0,0,1571,1572,5,73, + 0,0,1572,1573,5,78,0,0,1573,244,1,0,0,0,1574,1575,5,73,0,0,1575, + 1576,5,78,0,0,1576,1577,5,67,0,0,1577,1578,5,76,0,0,1578,1579,5, + 85,0,0,1579,1580,5,68,0,0,1580,1581,5,69,0,0,1581,246,1,0,0,0,1582, + 1583,5,73,0,0,1583,1584,5,78,0,0,1584,1585,5,68,0,0,1585,1586,5, + 69,0,0,1586,1587,5,88,0,0,1587,248,1,0,0,0,1588,1589,5,73,0,0,1589, + 1590,5,78,0,0,1590,1591,5,68,0,0,1591,1592,5,69,0,0,1592,1593,5, + 88,0,0,1593,1594,5,69,0,0,1594,1595,5,83,0,0,1595,250,1,0,0,0,1596, + 1597,5,73,0,0,1597,1598,5,78,0,0,1598,1599,5,78,0,0,1599,1600,5, + 69,0,0,1600,1601,5,82,0,0,1601,252,1,0,0,0,1602,1603,5,73,0,0,1603, + 1604,5,78,0,0,1604,1605,5,80,0,0,1605,1606,5,65,0,0,1606,1607,5, + 84,0,0,1607,1608,5,72,0,0,1608,254,1,0,0,0,1609,1610,5,73,0,0,1610, + 1611,5,78,0,0,1611,1612,5,80,0,0,1612,1613,5,85,0,0,1613,1614,5, + 84,0,0,1614,1615,5,70,0,0,1615,1616,5,79,0,0,1616,1617,5,82,0,0, + 1617,1618,5,77,0,0,1618,1619,5,65,0,0,1619,1620,5,84,0,0,1620,256, + 1,0,0,0,1621,1622,5,73,0,0,1622,1623,5,78,0,0,1623,1624,5,83,0,0, + 1624,1625,5,69,0,0,1625,1626,5,82,0,0,1626,1627,5,84,0,0,1627,258, + 1,0,0,0,1628,1629,5,73,0,0,1629,1630,5,78,0,0,1630,1631,5,84,0,0, + 1631,1632,5,69,0,0,1632,1633,5,82,0,0,1633,1634,5,83,0,0,1634,1635, + 5,69,0,0,1635,1636,5,67,0,0,1636,1637,5,84,0,0,1637,260,1,0,0,0, + 1638,1639,5,73,0,0,1639,1640,5,78,0,0,1640,1641,5,84,0,0,1641,1642, + 5,69,0,0,1642,1643,5,82,0,0,1643,1644,5,86,0,0,1644,1645,5,65,0, + 0,1645,1646,5,76,0,0,1646,262,1,0,0,0,1647,1648,5,73,0,0,1648,1649, + 5,78,0,0,1649,1650,5,84,0,0,1650,1651,5,79,0,0,1651,264,1,0,0,0, + 1652,1653,5,73,0,0,1653,1654,5,83,0,0,1654,266,1,0,0,0,1655,1656, + 5,73,0,0,1656,1657,5,84,0,0,1657,1658,5,69,0,0,1658,1659,5,77,0, + 0,1659,1660,5,83,0,0,1660,268,1,0,0,0,1661,1662,5,74,0,0,1662,1663, + 5,79,0,0,1663,1664,5,73,0,0,1664,1665,5,78,0,0,1665,270,1,0,0,0, + 1666,1667,5,75,0,0,1667,1668,5,69,0,0,1668,1669,5,89,0,0,1669,1670, + 5,83,0,0,1670,272,1,0,0,0,1671,1672,5,76,0,0,1672,1673,5,65,0,0, + 1673,1674,5,83,0,0,1674,1675,5,84,0,0,1675,274,1,0,0,0,1676,1677, + 5,76,0,0,1677,1678,5,65,0,0,1678,1679,5,84,0,0,1679,1680,5,69,0, + 0,1680,1681,5,82,0,0,1681,1682,5,65,0,0,1682,1683,5,76,0,0,1683, + 276,1,0,0,0,1684,1685,5,76,0,0,1685,1686,5,65,0,0,1686,1687,5,90, + 0,0,1687,1688,5,89,0,0,1688,278,1,0,0,0,1689,1690,5,76,0,0,1690, + 1691,5,69,0,0,1691,1692,5,65,0,0,1692,1693,5,68,0,0,1693,1694,5, + 73,0,0,1694,1695,5,78,0,0,1695,1696,5,71,0,0,1696,280,1,0,0,0,1697, + 1698,5,76,0,0,1698,1699,5,69,0,0,1699,1700,5,70,0,0,1700,1701,5, + 84,0,0,1701,282,1,0,0,0,1702,1703,5,76,0,0,1703,1704,5,73,0,0,1704, + 1705,5,75,0,0,1705,1706,5,69,0,0,1706,284,1,0,0,0,1707,1708,5,73, + 0,0,1708,1709,5,76,0,0,1709,1710,5,73,0,0,1710,1711,5,75,0,0,1711, + 1712,5,69,0,0,1712,286,1,0,0,0,1713,1714,5,76,0,0,1714,1715,5,73, + 0,0,1715,1716,5,77,0,0,1716,1717,5,73,0,0,1717,1718,5,84,0,0,1718, + 288,1,0,0,0,1719,1720,5,76,0,0,1720,1721,5,73,0,0,1721,1722,5,78, + 0,0,1722,1723,5,69,0,0,1723,1724,5,83,0,0,1724,290,1,0,0,0,1725, + 1726,5,76,0,0,1726,1727,5,73,0,0,1727,1728,5,83,0,0,1728,1729,5, + 84,0,0,1729,292,1,0,0,0,1730,1731,5,76,0,0,1731,1732,5,79,0,0,1732, + 1733,5,65,0,0,1733,1734,5,68,0,0,1734,294,1,0,0,0,1735,1736,5,76, + 0,0,1736,1737,5,79,0,0,1737,1738,5,67,0,0,1738,1739,5,65,0,0,1739, + 1740,5,76,0,0,1740,296,1,0,0,0,1741,1742,5,76,0,0,1742,1743,5,79, + 0,0,1743,1744,5,67,0,0,1744,1745,5,65,0,0,1745,1746,5,84,0,0,1746, + 1747,5,73,0,0,1747,1748,5,79,0,0,1748,1749,5,78,0,0,1749,298,1,0, + 0,0,1750,1751,5,76,0,0,1751,1752,5,79,0,0,1752,1753,5,67,0,0,1753, + 1754,5,75,0,0,1754,300,1,0,0,0,1755,1756,5,76,0,0,1756,1757,5,79, + 0,0,1757,1758,5,67,0,0,1758,1759,5,75,0,0,1759,1760,5,83,0,0,1760, + 302,1,0,0,0,1761,1762,5,76,0,0,1762,1763,5,79,0,0,1763,1764,5,71, + 0,0,1764,1765,5,73,0,0,1765,1766,5,67,0,0,1766,1767,5,65,0,0,1767, + 1768,5,76,0,0,1768,304,1,0,0,0,1769,1770,5,77,0,0,1770,1771,5,65, + 0,0,1771,1772,5,67,0,0,1772,1773,5,82,0,0,1773,1774,5,79,0,0,1774, + 306,1,0,0,0,1775,1776,5,77,0,0,1776,1777,5,65,0,0,1777,1778,5,80, + 0,0,1778,308,1,0,0,0,1779,1780,5,77,0,0,1780,1781,5,65,0,0,1781, + 1782,5,84,0,0,1782,1783,5,67,0,0,1783,1784,5,72,0,0,1784,1785,5, + 69,0,0,1785,1786,5,68,0,0,1786,310,1,0,0,0,1787,1788,5,77,0,0,1788, + 1789,5,69,0,0,1789,1790,5,82,0,0,1790,1791,5,71,0,0,1791,1792,5, + 69,0,0,1792,312,1,0,0,0,1793,1794,5,77,0,0,1794,1795,5,73,0,0,1795, + 1796,5,67,0,0,1796,1797,5,82,0,0,1797,1798,5,79,0,0,1798,1799,5, + 83,0,0,1799,1800,5,69,0,0,1800,1801,5,67,0,0,1801,1802,5,79,0,0, + 1802,1803,5,78,0,0,1803,1804,5,68,0,0,1804,314,1,0,0,0,1805,1806, + 5,77,0,0,1806,1807,5,73,0,0,1807,1808,5,67,0,0,1808,1809,5,82,0, + 0,1809,1810,5,79,0,0,1810,1811,5,83,0,0,1811,1812,5,69,0,0,1812, + 1813,5,67,0,0,1813,1814,5,79,0,0,1814,1815,5,78,0,0,1815,1816,5, + 68,0,0,1816,1817,5,83,0,0,1817,316,1,0,0,0,1818,1819,5,77,0,0,1819, + 1820,5,73,0,0,1820,1821,5,76,0,0,1821,1822,5,76,0,0,1822,1823,5, + 73,0,0,1823,1824,5,83,0,0,1824,1825,5,69,0,0,1825,1826,5,67,0,0, + 1826,1827,5,79,0,0,1827,1828,5,78,0,0,1828,1829,5,68,0,0,1829,318, + 1,0,0,0,1830,1831,5,77,0,0,1831,1832,5,73,0,0,1832,1833,5,76,0,0, + 1833,1834,5,76,0,0,1834,1835,5,73,0,0,1835,1836,5,83,0,0,1836,1837, + 5,69,0,0,1837,1838,5,67,0,0,1838,1839,5,79,0,0,1839,1840,5,78,0, + 0,1840,1841,5,68,0,0,1841,1842,5,83,0,0,1842,320,1,0,0,0,1843,1844, + 5,77,0,0,1844,1845,5,73,0,0,1845,1846,5,78,0,0,1846,1847,5,85,0, + 0,1847,1848,5,84,0,0,1848,1849,5,69,0,0,1849,322,1,0,0,0,1850,1851, + 5,77,0,0,1851,1852,5,73,0,0,1852,1853,5,78,0,0,1853,1854,5,85,0, + 0,1854,1855,5,84,0,0,1855,1856,5,69,0,0,1856,1857,5,83,0,0,1857, + 324,1,0,0,0,1858,1859,5,77,0,0,1859,1860,5,79,0,0,1860,1861,5,78, + 0,0,1861,1862,5,84,0,0,1862,1863,5,72,0,0,1863,326,1,0,0,0,1864, + 1865,5,77,0,0,1865,1866,5,79,0,0,1866,1867,5,78,0,0,1867,1868,5, + 84,0,0,1868,1869,5,72,0,0,1869,1870,5,83,0,0,1870,328,1,0,0,0,1871, + 1872,5,77,0,0,1872,1873,5,83,0,0,1873,1874,5,67,0,0,1874,1875,5, + 75,0,0,1875,330,1,0,0,0,1876,1877,5,78,0,0,1877,1878,5,65,0,0,1878, + 1879,5,77,0,0,1879,1880,5,69,0,0,1880,1881,5,83,0,0,1881,1882,5, + 80,0,0,1882,1883,5,65,0,0,1883,1884,5,67,0,0,1884,1885,5,69,0,0, + 1885,332,1,0,0,0,1886,1887,5,78,0,0,1887,1888,5,65,0,0,1888,1889, + 5,77,0,0,1889,1890,5,69,0,0,1890,1891,5,83,0,0,1891,1892,5,80,0, + 0,1892,1893,5,65,0,0,1893,1894,5,67,0,0,1894,1895,5,69,0,0,1895, + 1896,5,83,0,0,1896,334,1,0,0,0,1897,1898,5,78,0,0,1898,1899,5,65, + 0,0,1899,1900,5,78,0,0,1900,1901,5,79,0,0,1901,1902,5,83,0,0,1902, + 1903,5,69,0,0,1903,1904,5,67,0,0,1904,1905,5,79,0,0,1905,1906,5, + 78,0,0,1906,1907,5,68,0,0,1907,336,1,0,0,0,1908,1909,5,78,0,0,1909, + 1910,5,65,0,0,1910,1911,5,78,0,0,1911,1912,5,79,0,0,1912,1913,5, + 83,0,0,1913,1914,5,69,0,0,1914,1915,5,67,0,0,1915,1916,5,79,0,0, + 1916,1917,5,78,0,0,1917,1918,5,68,0,0,1918,1919,5,83,0,0,1919,338, + 1,0,0,0,1920,1921,5,78,0,0,1921,1922,5,65,0,0,1922,1923,5,84,0,0, + 1923,1924,5,85,0,0,1924,1925,5,82,0,0,1925,1926,5,65,0,0,1926,1927, + 5,76,0,0,1927,340,1,0,0,0,1928,1929,5,78,0,0,1929,1930,5,79,0,0, + 1930,342,1,0,0,0,1931,1932,5,78,0,0,1932,1933,5,79,0,0,1933,1936, + 5,84,0,0,1934,1936,5,33,0,0,1935,1931,1,0,0,0,1935,1934,1,0,0,0, + 1936,344,1,0,0,0,1937,1938,5,78,0,0,1938,1939,5,85,0,0,1939,1940, + 5,76,0,0,1940,1941,5,76,0,0,1941,346,1,0,0,0,1942,1943,5,78,0,0, + 1943,1944,5,85,0,0,1944,1945,5,76,0,0,1945,1946,5,76,0,0,1946,1947, + 5,83,0,0,1947,348,1,0,0,0,1948,1949,5,79,0,0,1949,1950,5,70,0,0, + 1950,350,1,0,0,0,1951,1952,5,79,0,0,1952,1953,5,70,0,0,1953,1954, + 5,70,0,0,1954,1955,5,83,0,0,1955,1956,5,69,0,0,1956,1957,5,84,0, + 0,1957,352,1,0,0,0,1958,1959,5,79,0,0,1959,1960,5,78,0,0,1960,354, + 1,0,0,0,1961,1962,5,79,0,0,1962,1963,5,78,0,0,1963,1964,5,76,0,0, + 1964,1965,5,89,0,0,1965,356,1,0,0,0,1966,1967,5,79,0,0,1967,1968, + 5,80,0,0,1968,1969,5,84,0,0,1969,1970,5,73,0,0,1970,1971,5,79,0, + 0,1971,1972,5,78,0,0,1972,358,1,0,0,0,1973,1974,5,79,0,0,1974,1975, + 5,80,0,0,1975,1976,5,84,0,0,1976,1977,5,73,0,0,1977,1978,5,79,0, + 0,1978,1979,5,78,0,0,1979,1980,5,83,0,0,1980,360,1,0,0,0,1981,1982, + 5,79,0,0,1982,1983,5,82,0,0,1983,362,1,0,0,0,1984,1985,5,79,0,0, + 1985,1986,5,82,0,0,1986,1987,5,68,0,0,1987,1988,5,69,0,0,1988,1989, + 5,82,0,0,1989,364,1,0,0,0,1990,1991,5,79,0,0,1991,1992,5,85,0,0, + 1992,1993,5,84,0,0,1993,366,1,0,0,0,1994,1995,5,79,0,0,1995,1996, + 5,85,0,0,1996,1997,5,84,0,0,1997,1998,5,69,0,0,1998,1999,5,82,0, + 0,1999,368,1,0,0,0,2000,2001,5,79,0,0,2001,2002,5,85,0,0,2002,2003, + 5,84,0,0,2003,2004,5,80,0,0,2004,2005,5,85,0,0,2005,2006,5,84,0, + 0,2006,2007,5,70,0,0,2007,2008,5,79,0,0,2008,2009,5,82,0,0,2009, + 2010,5,77,0,0,2010,2011,5,65,0,0,2011,2012,5,84,0,0,2012,370,1,0, + 0,0,2013,2014,5,79,0,0,2014,2015,5,86,0,0,2015,2016,5,69,0,0,2016, + 2017,5,82,0,0,2017,372,1,0,0,0,2018,2019,5,79,0,0,2019,2020,5,86, + 0,0,2020,2021,5,69,0,0,2021,2022,5,82,0,0,2022,2023,5,76,0,0,2023, + 2024,5,65,0,0,2024,2025,5,80,0,0,2025,2026,5,83,0,0,2026,374,1,0, + 0,0,2027,2028,5,79,0,0,2028,2029,5,86,0,0,2029,2030,5,69,0,0,2030, + 2031,5,82,0,0,2031,2032,5,76,0,0,2032,2033,5,65,0,0,2033,2034,5, + 89,0,0,2034,376,1,0,0,0,2035,2036,5,79,0,0,2036,2037,5,86,0,0,2037, + 2038,5,69,0,0,2038,2039,5,82,0,0,2039,2040,5,87,0,0,2040,2041,5, + 82,0,0,2041,2042,5,73,0,0,2042,2043,5,84,0,0,2043,2044,5,69,0,0, + 2044,378,1,0,0,0,2045,2046,5,80,0,0,2046,2047,5,65,0,0,2047,2048, + 5,82,0,0,2048,2049,5,84,0,0,2049,2050,5,73,0,0,2050,2051,5,84,0, + 0,2051,2052,5,73,0,0,2052,2053,5,79,0,0,2053,2054,5,78,0,0,2054, + 380,1,0,0,0,2055,2056,5,80,0,0,2056,2057,5,65,0,0,2057,2058,5,82, + 0,0,2058,2059,5,84,0,0,2059,2060,5,73,0,0,2060,2061,5,84,0,0,2061, + 2062,5,73,0,0,2062,2063,5,79,0,0,2063,2064,5,78,0,0,2064,2065,5, + 69,0,0,2065,2066,5,68,0,0,2066,382,1,0,0,0,2067,2068,5,80,0,0,2068, + 2069,5,65,0,0,2069,2070,5,82,0,0,2070,2071,5,84,0,0,2071,2072,5, + 73,0,0,2072,2073,5,84,0,0,2073,2074,5,73,0,0,2074,2075,5,79,0,0, + 2075,2076,5,78,0,0,2076,2077,5,83,0,0,2077,384,1,0,0,0,2078,2079, + 5,80,0,0,2079,2080,5,69,0,0,2080,2081,5,82,0,0,2081,2082,5,67,0, + 0,2082,2083,5,69,0,0,2083,2084,5,78,0,0,2084,2085,5,84,0,0,2085, + 2086,5,73,0,0,2086,2087,5,76,0,0,2087,2088,5,69,0,0,2088,2089,5, + 95,0,0,2089,2090,5,67,0,0,2090,2091,5,79,0,0,2091,2092,5,78,0,0, + 2092,2093,5,84,0,0,2093,386,1,0,0,0,2094,2095,5,80,0,0,2095,2096, + 5,69,0,0,2096,2097,5,82,0,0,2097,2098,5,67,0,0,2098,2099,5,69,0, + 0,2099,2100,5,78,0,0,2100,2101,5,84,0,0,2101,2102,5,73,0,0,2102, + 2103,5,76,0,0,2103,2104,5,69,0,0,2104,2105,5,95,0,0,2105,2106,5, + 68,0,0,2106,2107,5,73,0,0,2107,2108,5,83,0,0,2108,2109,5,67,0,0, + 2109,388,1,0,0,0,2110,2111,5,80,0,0,2111,2112,5,69,0,0,2112,2113, + 5,82,0,0,2113,2114,5,67,0,0,2114,2115,5,69,0,0,2115,2116,5,78,0, + 0,2116,2117,5,84,0,0,2117,390,1,0,0,0,2118,2119,5,80,0,0,2119,2120, + 5,73,0,0,2120,2121,5,86,0,0,2121,2122,5,79,0,0,2122,2123,5,84,0, + 0,2123,392,1,0,0,0,2124,2125,5,80,0,0,2125,2126,5,76,0,0,2126,2127, + 5,65,0,0,2127,2128,5,67,0,0,2128,2129,5,73,0,0,2129,2130,5,78,0, + 0,2130,2131,5,71,0,0,2131,394,1,0,0,0,2132,2133,5,80,0,0,2133,2134, + 5,79,0,0,2134,2135,5,83,0,0,2135,2136,5,73,0,0,2136,2137,5,84,0, + 0,2137,2138,5,73,0,0,2138,2139,5,79,0,0,2139,2140,5,78,0,0,2140, + 396,1,0,0,0,2141,2142,5,80,0,0,2142,2143,5,82,0,0,2143,2144,5,69, + 0,0,2144,2145,5,67,0,0,2145,2146,5,69,0,0,2146,2147,5,68,0,0,2147, + 2148,5,73,0,0,2148,2149,5,78,0,0,2149,2150,5,71,0,0,2150,398,1,0, + 0,0,2151,2152,5,80,0,0,2152,2153,5,82,0,0,2153,2154,5,73,0,0,2154, + 2155,5,77,0,0,2155,2156,5,65,0,0,2156,2157,5,82,0,0,2157,2158,5, + 89,0,0,2158,400,1,0,0,0,2159,2160,5,80,0,0,2160,2161,5,82,0,0,2161, + 2162,5,73,0,0,2162,2163,5,78,0,0,2163,2164,5,67,0,0,2164,2165,5, + 73,0,0,2165,2166,5,80,0,0,2166,2167,5,65,0,0,2167,2168,5,76,0,0, + 2168,2169,5,83,0,0,2169,402,1,0,0,0,2170,2171,5,80,0,0,2171,2172, + 5,82,0,0,2172,2173,5,79,0,0,2173,2174,5,80,0,0,2174,2175,5,69,0, + 0,2175,2176,5,82,0,0,2176,2177,5,84,0,0,2177,2178,5,73,0,0,2178, + 2179,5,69,0,0,2179,2180,5,83,0,0,2180,404,1,0,0,0,2181,2182,5,80, + 0,0,2182,2183,5,85,0,0,2183,2184,5,82,0,0,2184,2185,5,71,0,0,2185, + 2186,5,69,0,0,2186,406,1,0,0,0,2187,2188,5,81,0,0,2188,2189,5,85, + 0,0,2189,2190,5,65,0,0,2190,2191,5,82,0,0,2191,2192,5,84,0,0,2192, + 2193,5,69,0,0,2193,2194,5,82,0,0,2194,408,1,0,0,0,2195,2196,5,81, + 0,0,2196,2197,5,85,0,0,2197,2198,5,69,0,0,2198,2199,5,82,0,0,2199, + 2200,5,89,0,0,2200,410,1,0,0,0,2201,2202,5,82,0,0,2202,2203,5,65, + 0,0,2203,2204,5,78,0,0,2204,2205,5,71,0,0,2205,2206,5,69,0,0,2206, + 412,1,0,0,0,2207,2208,5,82,0,0,2208,2209,5,69,0,0,2209,2210,5,67, + 0,0,2210,2211,5,79,0,0,2211,2212,5,82,0,0,2212,2213,5,68,0,0,2213, + 2214,5,82,0,0,2214,2215,5,69,0,0,2215,2216,5,65,0,0,2216,2217,5, + 68,0,0,2217,2218,5,69,0,0,2218,2219,5,82,0,0,2219,414,1,0,0,0,2220, + 2221,5,82,0,0,2221,2222,5,69,0,0,2222,2223,5,67,0,0,2223,2224,5, + 79,0,0,2224,2225,5,82,0,0,2225,2226,5,68,0,0,2226,2227,5,87,0,0, + 2227,2228,5,82,0,0,2228,2229,5,73,0,0,2229,2230,5,84,0,0,2230,2231, + 5,69,0,0,2231,2232,5,82,0,0,2232,416,1,0,0,0,2233,2234,5,82,0,0, + 2234,2235,5,69,0,0,2235,2236,5,67,0,0,2236,2237,5,79,0,0,2237,2238, + 5,86,0,0,2238,2239,5,69,0,0,2239,2240,5,82,0,0,2240,418,1,0,0,0, + 2241,2242,5,82,0,0,2242,2243,5,69,0,0,2243,2244,5,68,0,0,2244,2245, + 5,85,0,0,2245,2246,5,67,0,0,2246,2247,5,69,0,0,2247,420,1,0,0,0, + 2248,2249,5,82,0,0,2249,2250,5,69,0,0,2250,2251,5,70,0,0,2251,2252, + 5,69,0,0,2252,2253,5,82,0,0,2253,2254,5,69,0,0,2254,2255,5,78,0, + 0,2255,2256,5,67,0,0,2256,2257,5,69,0,0,2257,2258,5,83,0,0,2258, + 422,1,0,0,0,2259,2260,5,82,0,0,2260,2261,5,69,0,0,2261,2262,5,70, + 0,0,2262,2263,5,82,0,0,2263,2264,5,69,0,0,2264,2265,5,83,0,0,2265, + 2266,5,72,0,0,2266,424,1,0,0,0,2267,2268,5,82,0,0,2268,2269,5,69, + 0,0,2269,2270,5,78,0,0,2270,2271,5,65,0,0,2271,2272,5,77,0,0,2272, + 2273,5,69,0,0,2273,426,1,0,0,0,2274,2275,5,82,0,0,2275,2276,5,69, + 0,0,2276,2277,5,80,0,0,2277,2278,5,65,0,0,2278,2279,5,73,0,0,2279, + 2280,5,82,0,0,2280,428,1,0,0,0,2281,2282,5,82,0,0,2282,2283,5,69, + 0,0,2283,2284,5,80,0,0,2284,2285,5,69,0,0,2285,2286,5,65,0,0,2286, + 2287,5,84,0,0,2287,2288,5,65,0,0,2288,2289,5,66,0,0,2289,2290,5, + 76,0,0,2290,2291,5,69,0,0,2291,430,1,0,0,0,2292,2293,5,82,0,0,2293, + 2294,5,69,0,0,2294,2295,5,80,0,0,2295,2296,5,76,0,0,2296,2297,5, + 65,0,0,2297,2298,5,67,0,0,2298,2299,5,69,0,0,2299,432,1,0,0,0,2300, + 2301,5,82,0,0,2301,2302,5,69,0,0,2302,2303,5,83,0,0,2303,2304,5, + 69,0,0,2304,2305,5,84,0,0,2305,434,1,0,0,0,2306,2307,5,82,0,0,2307, + 2308,5,69,0,0,2308,2309,5,83,0,0,2309,2310,5,80,0,0,2310,2311,5, + 69,0,0,2311,2312,5,67,0,0,2312,2313,5,84,0,0,2313,436,1,0,0,0,2314, + 2315,5,82,0,0,2315,2316,5,69,0,0,2316,2317,5,83,0,0,2317,2318,5, + 84,0,0,2318,2319,5,82,0,0,2319,2320,5,73,0,0,2320,2321,5,67,0,0, + 2321,2322,5,84,0,0,2322,438,1,0,0,0,2323,2324,5,82,0,0,2324,2325, + 5,69,0,0,2325,2326,5,86,0,0,2326,2327,5,79,0,0,2327,2328,5,75,0, + 0,2328,2329,5,69,0,0,2329,440,1,0,0,0,2330,2331,5,82,0,0,2331,2332, + 5,73,0,0,2332,2333,5,71,0,0,2333,2334,5,72,0,0,2334,2335,5,84,0, + 0,2335,442,1,0,0,0,2336,2337,5,82,0,0,2337,2338,5,76,0,0,2338,2339, + 5,73,0,0,2339,2340,5,75,0,0,2340,2348,5,69,0,0,2341,2342,5,82,0, + 0,2342,2343,5,69,0,0,2343,2344,5,71,0,0,2344,2345,5,69,0,0,2345, + 2346,5,88,0,0,2346,2348,5,80,0,0,2347,2336,1,0,0,0,2347,2341,1,0, + 0,0,2348,444,1,0,0,0,2349,2350,5,82,0,0,2350,2351,5,79,0,0,2351, + 2352,5,76,0,0,2352,2353,5,69,0,0,2353,446,1,0,0,0,2354,2355,5,82, + 0,0,2355,2356,5,79,0,0,2356,2357,5,76,0,0,2357,2358,5,69,0,0,2358, + 2359,5,83,0,0,2359,448,1,0,0,0,2360,2361,5,82,0,0,2361,2362,5,79, + 0,0,2362,2363,5,76,0,0,2363,2364,5,76,0,0,2364,2365,5,66,0,0,2365, + 2366,5,65,0,0,2366,2367,5,67,0,0,2367,2368,5,75,0,0,2368,450,1,0, + 0,0,2369,2370,5,82,0,0,2370,2371,5,79,0,0,2371,2372,5,76,0,0,2372, + 2373,5,76,0,0,2373,2374,5,85,0,0,2374,2375,5,80,0,0,2375,452,1,0, + 0,0,2376,2377,5,82,0,0,2377,2378,5,79,0,0,2378,2379,5,87,0,0,2379, + 454,1,0,0,0,2380,2381,5,82,0,0,2381,2382,5,79,0,0,2382,2383,5,87, + 0,0,2383,2384,5,83,0,0,2384,456,1,0,0,0,2385,2386,5,83,0,0,2386, + 2387,5,69,0,0,2387,2388,5,67,0,0,2388,2389,5,79,0,0,2389,2390,5, + 78,0,0,2390,2391,5,68,0,0,2391,458,1,0,0,0,2392,2393,5,83,0,0,2393, + 2394,5,69,0,0,2394,2395,5,67,0,0,2395,2396,5,79,0,0,2396,2397,5, + 78,0,0,2397,2398,5,68,0,0,2398,2399,5,83,0,0,2399,460,1,0,0,0,2400, + 2401,5,83,0,0,2401,2402,5,67,0,0,2402,2403,5,72,0,0,2403,2404,5, + 69,0,0,2404,2405,5,77,0,0,2405,2406,5,65,0,0,2406,462,1,0,0,0,2407, + 2408,5,83,0,0,2408,2409,5,67,0,0,2409,2410,5,72,0,0,2410,2411,5, + 69,0,0,2411,2412,5,77,0,0,2412,2413,5,65,0,0,2413,2414,5,83,0,0, + 2414,464,1,0,0,0,2415,2416,5,83,0,0,2416,2417,5,69,0,0,2417,2418, + 5,76,0,0,2418,2419,5,69,0,0,2419,2420,5,67,0,0,2420,2421,5,84,0, + 0,2421,466,1,0,0,0,2422,2423,5,83,0,0,2423,2424,5,69,0,0,2424,2425, + 5,77,0,0,2425,2426,5,73,0,0,2426,468,1,0,0,0,2427,2428,5,83,0,0, + 2428,2429,5,69,0,0,2429,2430,5,80,0,0,2430,2431,5,65,0,0,2431,2432, + 5,82,0,0,2432,2433,5,65,0,0,2433,2434,5,84,0,0,2434,2435,5,69,0, + 0,2435,2436,5,68,0,0,2436,470,1,0,0,0,2437,2438,5,83,0,0,2438,2439, + 5,69,0,0,2439,2440,5,82,0,0,2440,2441,5,68,0,0,2441,2442,5,69,0, + 0,2442,472,1,0,0,0,2443,2444,5,83,0,0,2444,2445,5,69,0,0,2445,2446, + 5,82,0,0,2446,2447,5,68,0,0,2447,2448,5,69,0,0,2448,2449,5,80,0, + 0,2449,2450,5,82,0,0,2450,2451,5,79,0,0,2451,2452,5,80,0,0,2452, + 2453,5,69,0,0,2453,2454,5,82,0,0,2454,2455,5,84,0,0,2455,2456,5, + 73,0,0,2456,2457,5,69,0,0,2457,2458,5,83,0,0,2458,474,1,0,0,0,2459, + 2460,5,83,0,0,2460,2461,5,69,0,0,2461,2462,5,83,0,0,2462,2463,5, + 83,0,0,2463,2464,5,73,0,0,2464,2465,5,79,0,0,2465,2466,5,78,0,0, + 2466,2467,5,95,0,0,2467,2468,5,85,0,0,2468,2469,5,83,0,0,2469,2470, + 5,69,0,0,2470,2471,5,82,0,0,2471,476,1,0,0,0,2472,2473,5,83,0,0, + 2473,2474,5,69,0,0,2474,2475,5,84,0,0,2475,478,1,0,0,0,2476,2477, + 5,77,0,0,2477,2478,5,73,0,0,2478,2479,5,78,0,0,2479,2480,5,85,0, + 0,2480,2481,5,83,0,0,2481,480,1,0,0,0,2482,2483,5,83,0,0,2483,2484, + 5,69,0,0,2484,2485,5,84,0,0,2485,2486,5,83,0,0,2486,482,1,0,0,0, + 2487,2488,5,83,0,0,2488,2489,5,72,0,0,2489,2490,5,79,0,0,2490,2491, + 5,87,0,0,2491,484,1,0,0,0,2492,2493,5,83,0,0,2493,2494,5,75,0,0, + 2494,2495,5,69,0,0,2495,2496,5,87,0,0,2496,2497,5,69,0,0,2497,2498, + 5,68,0,0,2498,486,1,0,0,0,2499,2500,5,83,0,0,2500,2501,5,79,0,0, + 2501,2502,5,77,0,0,2502,2503,5,69,0,0,2503,488,1,0,0,0,2504,2505, + 5,83,0,0,2505,2506,5,79,0,0,2506,2507,5,82,0,0,2507,2508,5,84,0, + 0,2508,490,1,0,0,0,2509,2510,5,83,0,0,2510,2511,5,79,0,0,2511,2512, + 5,82,0,0,2512,2513,5,84,0,0,2513,2514,5,69,0,0,2514,2515,5,68,0, + 0,2515,492,1,0,0,0,2516,2517,5,83,0,0,2517,2518,5,79,0,0,2518,2519, + 5,85,0,0,2519,2520,5,82,0,0,2520,2521,5,67,0,0,2521,2522,5,69,0, + 0,2522,494,1,0,0,0,2523,2524,5,83,0,0,2524,2525,5,84,0,0,2525,2526, + 5,65,0,0,2526,2527,5,82,0,0,2527,2528,5,84,0,0,2528,496,1,0,0,0, + 2529,2530,5,83,0,0,2530,2531,5,84,0,0,2531,2532,5,65,0,0,2532,2533, + 5,84,0,0,2533,2534,5,73,0,0,2534,2535,5,83,0,0,2535,2536,5,84,0, + 0,2536,2537,5,73,0,0,2537,2538,5,67,0,0,2538,2539,5,83,0,0,2539, + 498,1,0,0,0,2540,2541,5,83,0,0,2541,2542,5,84,0,0,2542,2543,5,79, + 0,0,2543,2544,5,82,0,0,2544,2545,5,69,0,0,2545,2546,5,68,0,0,2546, + 500,1,0,0,0,2547,2548,5,83,0,0,2548,2549,5,84,0,0,2549,2550,5,82, + 0,0,2550,2551,5,65,0,0,2551,2552,5,84,0,0,2552,2553,5,73,0,0,2553, + 2554,5,70,0,0,2554,2555,5,89,0,0,2555,502,1,0,0,0,2556,2557,5,83, + 0,0,2557,2558,5,84,0,0,2558,2559,5,82,0,0,2559,2560,5,85,0,0,2560, + 2561,5,67,0,0,2561,2562,5,84,0,0,2562,504,1,0,0,0,2563,2564,5,83, + 0,0,2564,2565,5,85,0,0,2565,2566,5,66,0,0,2566,2567,5,83,0,0,2567, + 2568,5,84,0,0,2568,2569,5,82,0,0,2569,506,1,0,0,0,2570,2571,5,83, + 0,0,2571,2572,5,85,0,0,2572,2573,5,66,0,0,2573,2574,5,83,0,0,2574, + 2575,5,84,0,0,2575,2576,5,82,0,0,2576,2577,5,73,0,0,2577,2578,5, + 78,0,0,2578,2579,5,71,0,0,2579,508,1,0,0,0,2580,2581,5,83,0,0,2581, + 2582,5,89,0,0,2582,2583,5,78,0,0,2583,2584,5,67,0,0,2584,510,1,0, + 0,0,2585,2586,5,83,0,0,2586,2587,5,89,0,0,2587,2588,5,83,0,0,2588, + 2589,5,84,0,0,2589,2590,5,69,0,0,2590,2591,5,77,0,0,2591,2592,5, + 95,0,0,2592,2593,5,84,0,0,2593,2594,5,73,0,0,2594,2595,5,77,0,0, + 2595,2596,5,69,0,0,2596,512,1,0,0,0,2597,2598,5,83,0,0,2598,2599, + 5,89,0,0,2599,2600,5,83,0,0,2600,2601,5,84,0,0,2601,2602,5,69,0, + 0,2602,2603,5,77,0,0,2603,2604,5,95,0,0,2604,2605,5,86,0,0,2605, + 2606,5,69,0,0,2606,2607,5,82,0,0,2607,2608,5,83,0,0,2608,2609,5, + 73,0,0,2609,2610,5,79,0,0,2610,2611,5,78,0,0,2611,514,1,0,0,0,2612, + 2613,5,84,0,0,2613,2614,5,65,0,0,2614,2615,5,66,0,0,2615,2616,5, + 76,0,0,2616,2617,5,69,0,0,2617,516,1,0,0,0,2618,2619,5,84,0,0,2619, + 2620,5,65,0,0,2620,2621,5,66,0,0,2621,2622,5,76,0,0,2622,2623,5, + 69,0,0,2623,2624,5,83,0,0,2624,518,1,0,0,0,2625,2626,5,84,0,0,2626, + 2627,5,65,0,0,2627,2628,5,66,0,0,2628,2629,5,76,0,0,2629,2630,5, + 69,0,0,2630,2631,5,83,0,0,2631,2632,5,65,0,0,2632,2633,5,77,0,0, + 2633,2634,5,80,0,0,2634,2635,5,76,0,0,2635,2636,5,69,0,0,2636,520, + 1,0,0,0,2637,2638,5,84,0,0,2638,2639,5,65,0,0,2639,2640,5,82,0,0, + 2640,2641,5,71,0,0,2641,2642,5,69,0,0,2642,2643,5,84,0,0,2643,522, + 1,0,0,0,2644,2645,5,84,0,0,2645,2646,5,66,0,0,2646,2647,5,76,0,0, + 2647,2648,5,80,0,0,2648,2649,5,82,0,0,2649,2650,5,79,0,0,2650,2651, + 5,80,0,0,2651,2652,5,69,0,0,2652,2653,5,82,0,0,2653,2654,5,84,0, + 0,2654,2655,5,73,0,0,2655,2656,5,69,0,0,2656,2657,5,83,0,0,2657, + 524,1,0,0,0,2658,2659,5,84,0,0,2659,2660,5,69,0,0,2660,2661,5,77, + 0,0,2661,2662,5,80,0,0,2662,2663,5,79,0,0,2663,2664,5,82,0,0,2664, + 2665,5,65,0,0,2665,2666,5,82,0,0,2666,2672,5,89,0,0,2667,2668,5, + 84,0,0,2668,2669,5,69,0,0,2669,2670,5,77,0,0,2670,2672,5,80,0,0, + 2671,2658,1,0,0,0,2671,2667,1,0,0,0,2672,526,1,0,0,0,2673,2674,5, + 84,0,0,2674,2675,5,69,0,0,2675,2676,5,82,0,0,2676,2677,5,77,0,0, + 2677,2678,5,73,0,0,2678,2679,5,78,0,0,2679,2680,5,65,0,0,2680,2681, + 5,84,0,0,2681,2682,5,69,0,0,2682,2683,5,68,0,0,2683,528,1,0,0,0, + 2684,2685,5,84,0,0,2685,2686,5,72,0,0,2686,2687,5,69,0,0,2687,2688, + 5,78,0,0,2688,530,1,0,0,0,2689,2690,5,84,0,0,2690,2691,5,73,0,0, + 2691,2692,5,77,0,0,2692,2693,5,69,0,0,2693,532,1,0,0,0,2694,2695, + 5,84,0,0,2695,2696,5,73,0,0,2696,2697,5,77,0,0,2697,2698,5,69,0, + 0,2698,2699,5,83,0,0,2699,2700,5,84,0,0,2700,2701,5,65,0,0,2701, + 2702,5,77,0,0,2702,2703,5,80,0,0,2703,534,1,0,0,0,2704,2705,5,84, + 0,0,2705,2706,5,73,0,0,2706,2707,5,77,0,0,2707,2708,5,69,0,0,2708, + 2709,5,83,0,0,2709,2710,5,84,0,0,2710,2711,5,65,0,0,2711,2712,5, + 77,0,0,2712,2713,5,80,0,0,2713,2714,5,65,0,0,2714,2715,5,68,0,0, + 2715,2716,5,68,0,0,2716,536,1,0,0,0,2717,2718,5,84,0,0,2718,2719, + 5,73,0,0,2719,2720,5,77,0,0,2720,2721,5,69,0,0,2721,2722,5,83,0, + 0,2722,2723,5,84,0,0,2723,2724,5,65,0,0,2724,2725,5,77,0,0,2725, + 2726,5,80,0,0,2726,2727,5,68,0,0,2727,2728,5,73,0,0,2728,2729,5, + 70,0,0,2729,2730,5,70,0,0,2730,538,1,0,0,0,2731,2732,5,84,0,0,2732, + 2733,5,79,0,0,2733,540,1,0,0,0,2734,2735,5,84,0,0,2735,2736,5,79, + 0,0,2736,2737,5,85,0,0,2737,2738,5,67,0,0,2738,2739,5,72,0,0,2739, + 542,1,0,0,0,2740,2741,5,84,0,0,2741,2742,5,82,0,0,2742,2743,5,65, + 0,0,2743,2744,5,73,0,0,2744,2745,5,76,0,0,2745,2746,5,73,0,0,2746, + 2747,5,78,0,0,2747,2748,5,71,0,0,2748,544,1,0,0,0,2749,2750,5,84, + 0,0,2750,2751,5,82,0,0,2751,2752,5,65,0,0,2752,2753,5,78,0,0,2753, + 2754,5,83,0,0,2754,2755,5,65,0,0,2755,2756,5,67,0,0,2756,2757,5, + 84,0,0,2757,2758,5,73,0,0,2758,2759,5,79,0,0,2759,2760,5,78,0,0, + 2760,546,1,0,0,0,2761,2762,5,84,0,0,2762,2763,5,82,0,0,2763,2764, + 5,65,0,0,2764,2765,5,78,0,0,2765,2766,5,83,0,0,2766,2767,5,65,0, + 0,2767,2768,5,67,0,0,2768,2769,5,84,0,0,2769,2770,5,73,0,0,2770, + 2771,5,79,0,0,2771,2772,5,78,0,0,2772,2773,5,83,0,0,2773,548,1,0, + 0,0,2774,2775,5,84,0,0,2775,2776,5,82,0,0,2776,2777,5,65,0,0,2777, + 2778,5,78,0,0,2778,2779,5,83,0,0,2779,2780,5,70,0,0,2780,2781,5, + 79,0,0,2781,2782,5,82,0,0,2782,2783,5,77,0,0,2783,550,1,0,0,0,2784, + 2785,5,84,0,0,2785,2786,5,82,0,0,2786,2787,5,73,0,0,2787,2788,5, + 77,0,0,2788,552,1,0,0,0,2789,2790,5,84,0,0,2790,2791,5,82,0,0,2791, + 2792,5,85,0,0,2792,2793,5,69,0,0,2793,554,1,0,0,0,2794,2795,5,84, + 0,0,2795,2796,5,82,0,0,2796,2797,5,85,0,0,2797,2798,5,78,0,0,2798, + 2799,5,67,0,0,2799,2800,5,65,0,0,2800,2801,5,84,0,0,2801,2802,5, + 69,0,0,2802,556,1,0,0,0,2803,2804,5,84,0,0,2804,2805,5,82,0,0,2805, + 2806,5,89,0,0,2806,2807,5,95,0,0,2807,2808,5,67,0,0,2808,2809,5, + 65,0,0,2809,2810,5,83,0,0,2810,2811,5,84,0,0,2811,558,1,0,0,0,2812, + 2813,5,84,0,0,2813,2814,5,89,0,0,2814,2815,5,80,0,0,2815,2816,5, + 69,0,0,2816,560,1,0,0,0,2817,2818,5,85,0,0,2818,2819,5,78,0,0,2819, + 2820,5,65,0,0,2820,2821,5,82,0,0,2821,2822,5,67,0,0,2822,2823,5, + 72,0,0,2823,2824,5,73,0,0,2824,2825,5,86,0,0,2825,2826,5,69,0,0, + 2826,562,1,0,0,0,2827,2828,5,85,0,0,2828,2829,5,78,0,0,2829,2830, + 5,66,0,0,2830,2831,5,79,0,0,2831,2832,5,85,0,0,2832,2833,5,78,0, + 0,2833,2834,5,68,0,0,2834,2835,5,69,0,0,2835,2836,5,68,0,0,2836, + 564,1,0,0,0,2837,2838,5,85,0,0,2838,2839,5,78,0,0,2839,2840,5,67, + 0,0,2840,2841,5,65,0,0,2841,2842,5,67,0,0,2842,2843,5,72,0,0,2843, + 2844,5,69,0,0,2844,566,1,0,0,0,2845,2846,5,85,0,0,2846,2847,5,78, + 0,0,2847,2848,5,73,0,0,2848,2849,5,79,0,0,2849,2850,5,78,0,0,2850, + 568,1,0,0,0,2851,2852,5,85,0,0,2852,2853,5,78,0,0,2853,2854,5,73, + 0,0,2854,2855,5,81,0,0,2855,2856,5,85,0,0,2856,2857,5,69,0,0,2857, + 570,1,0,0,0,2858,2859,5,85,0,0,2859,2860,5,78,0,0,2860,2861,5,75, + 0,0,2861,2862,5,78,0,0,2862,2863,5,79,0,0,2863,2864,5,87,0,0,2864, + 2865,5,78,0,0,2865,572,1,0,0,0,2866,2867,5,85,0,0,2867,2868,5,78, + 0,0,2868,2869,5,76,0,0,2869,2870,5,79,0,0,2870,2871,5,67,0,0,2871, + 2872,5,75,0,0,2872,574,1,0,0,0,2873,2874,5,85,0,0,2874,2875,5,78, + 0,0,2875,2876,5,80,0,0,2876,2877,5,73,0,0,2877,2878,5,86,0,0,2878, + 2879,5,79,0,0,2879,2880,5,84,0,0,2880,576,1,0,0,0,2881,2882,5,85, + 0,0,2882,2883,5,78,0,0,2883,2884,5,83,0,0,2884,2885,5,69,0,0,2885, + 2886,5,84,0,0,2886,578,1,0,0,0,2887,2888,5,85,0,0,2888,2889,5,80, + 0,0,2889,2890,5,68,0,0,2890,2891,5,65,0,0,2891,2892,5,84,0,0,2892, + 2893,5,69,0,0,2893,580,1,0,0,0,2894,2895,5,85,0,0,2895,2896,5,83, + 0,0,2896,2897,5,69,0,0,2897,582,1,0,0,0,2898,2899,5,85,0,0,2899, + 2900,5,83,0,0,2900,2901,5,69,0,0,2901,2902,5,82,0,0,2902,584,1,0, + 0,0,2903,2904,5,85,0,0,2904,2905,5,83,0,0,2905,2906,5,73,0,0,2906, + 2907,5,78,0,0,2907,2908,5,71,0,0,2908,586,1,0,0,0,2909,2910,5,86, + 0,0,2910,2911,5,65,0,0,2911,2912,5,76,0,0,2912,2913,5,85,0,0,2913, + 2914,5,69,0,0,2914,2915,5,83,0,0,2915,588,1,0,0,0,2916,2917,5,86, + 0,0,2917,2918,5,69,0,0,2918,2919,5,82,0,0,2919,2920,5,83,0,0,2920, + 2921,5,73,0,0,2921,2922,5,79,0,0,2922,2923,5,78,0,0,2923,590,1,0, + 0,0,2924,2925,5,86,0,0,2925,2926,5,73,0,0,2926,2927,5,69,0,0,2927, + 2928,5,87,0,0,2928,592,1,0,0,0,2929,2930,5,86,0,0,2930,2931,5,73, + 0,0,2931,2932,5,69,0,0,2932,2933,5,87,0,0,2933,2934,5,83,0,0,2934, + 594,1,0,0,0,2935,2936,5,87,0,0,2936,2937,5,69,0,0,2937,2938,5,69, + 0,0,2938,2939,5,75,0,0,2939,596,1,0,0,0,2940,2941,5,87,0,0,2941, + 2942,5,69,0,0,2942,2943,5,69,0,0,2943,2944,5,75,0,0,2944,2945,5, + 83,0,0,2945,598,1,0,0,0,2946,2947,5,87,0,0,2947,2948,5,72,0,0,2948, + 2949,5,69,0,0,2949,2950,5,78,0,0,2950,600,1,0,0,0,2951,2952,5,87, + 0,0,2952,2953,5,72,0,0,2953,2954,5,69,0,0,2954,2955,5,82,0,0,2955, + 2956,5,69,0,0,2956,602,1,0,0,0,2957,2958,5,87,0,0,2958,2959,5,73, + 0,0,2959,2960,5,78,0,0,2960,2961,5,68,0,0,2961,2962,5,79,0,0,2962, + 2963,5,87,0,0,2963,604,1,0,0,0,2964,2965,5,87,0,0,2965,2966,5,73, + 0,0,2966,2967,5,84,0,0,2967,2968,5,72,0,0,2968,606,1,0,0,0,2969, + 2970,5,87,0,0,2970,2971,5,73,0,0,2971,2972,5,84,0,0,2972,2973,5, + 72,0,0,2973,2974,5,73,0,0,2974,2975,5,78,0,0,2975,608,1,0,0,0,2976, + 2977,5,89,0,0,2977,2978,5,69,0,0,2978,2979,5,65,0,0,2979,2980,5, + 82,0,0,2980,610,1,0,0,0,2981,2982,5,89,0,0,2982,2983,5,69,0,0,2983, + 2984,5,65,0,0,2984,2985,5,82,0,0,2985,2986,5,83,0,0,2986,612,1,0, + 0,0,2987,2988,5,90,0,0,2988,2989,5,79,0,0,2989,2990,5,78,0,0,2990, + 2991,5,69,0,0,2991,614,1,0,0,0,2992,2996,5,61,0,0,2993,2994,5,61, + 0,0,2994,2996,5,61,0,0,2995,2992,1,0,0,0,2995,2993,1,0,0,0,2996, + 616,1,0,0,0,2997,2998,5,60,0,0,2998,2999,5,61,0,0,2999,3000,5,62, + 0,0,3000,618,1,0,0,0,3001,3002,5,60,0,0,3002,3003,5,62,0,0,3003, + 620,1,0,0,0,3004,3005,5,33,0,0,3005,3006,5,61,0,0,3006,622,1,0,0, + 0,3007,3008,5,60,0,0,3008,624,1,0,0,0,3009,3010,5,60,0,0,3010,3014, + 5,61,0,0,3011,3012,5,33,0,0,3012,3014,5,62,0,0,3013,3009,1,0,0,0, + 3013,3011,1,0,0,0,3014,626,1,0,0,0,3015,3016,5,62,0,0,3016,628,1, + 0,0,0,3017,3018,5,62,0,0,3018,3022,5,61,0,0,3019,3020,5,33,0,0,3020, + 3022,5,60,0,0,3021,3017,1,0,0,0,3021,3019,1,0,0,0,3022,630,1,0,0, + 0,3023,3024,5,43,0,0,3024,632,1,0,0,0,3025,3026,5,45,0,0,3026,634, + 1,0,0,0,3027,3028,5,42,0,0,3028,636,1,0,0,0,3029,3030,5,47,0,0,3030, + 638,1,0,0,0,3031,3032,5,37,0,0,3032,640,1,0,0,0,3033,3034,5,126, + 0,0,3034,642,1,0,0,0,3035,3036,5,38,0,0,3036,644,1,0,0,0,3037,3038, + 5,124,0,0,3038,646,1,0,0,0,3039,3040,5,124,0,0,3040,3041,5,124,0, + 0,3041,648,1,0,0,0,3042,3043,5,94,0,0,3043,650,1,0,0,0,3044,3045, + 5,58,0,0,3045,652,1,0,0,0,3046,3047,5,45,0,0,3047,3048,5,62,0,0, + 3048,654,1,0,0,0,3049,3050,5,47,0,0,3050,3051,5,42,0,0,3051,3052, + 5,43,0,0,3052,656,1,0,0,0,3053,3054,5,42,0,0,3054,3055,5,47,0,0, + 3055,658,1,0,0,0,3056,3062,5,39,0,0,3057,3061,8,0,0,0,3058,3059, + 5,92,0,0,3059,3061,9,0,0,0,3060,3057,1,0,0,0,3060,3058,1,0,0,0,3061, + 3064,1,0,0,0,3062,3060,1,0,0,0,3062,3063,1,0,0,0,3063,3065,1,0,0, + 0,3064,3062,1,0,0,0,3065,3087,5,39,0,0,3066,3067,5,82,0,0,3067,3068, + 5,39,0,0,3068,3072,1,0,0,0,3069,3071,8,1,0,0,3070,3069,1,0,0,0,3071, + 3074,1,0,0,0,3072,3070,1,0,0,0,3072,3073,1,0,0,0,3073,3075,1,0,0, + 0,3074,3072,1,0,0,0,3075,3087,5,39,0,0,3076,3077,5,82,0,0,3077,3078, + 5,34,0,0,3078,3082,1,0,0,0,3079,3081,8,2,0,0,3080,3079,1,0,0,0,3081, + 3084,1,0,0,0,3082,3080,1,0,0,0,3082,3083,1,0,0,0,3083,3085,1,0,0, + 0,3084,3082,1,0,0,0,3085,3087,5,34,0,0,3086,3056,1,0,0,0,3086,3066, + 1,0,0,0,3086,3076,1,0,0,0,3087,660,1,0,0,0,3088,3094,5,34,0,0,3089, + 3093,8,3,0,0,3090,3091,5,92,0,0,3091,3093,9,0,0,0,3092,3089,1,0, + 0,0,3092,3090,1,0,0,0,3093,3096,1,0,0,0,3094,3092,1,0,0,0,3094,3095, + 1,0,0,0,3095,3097,1,0,0,0,3096,3094,1,0,0,0,3097,3098,5,34,0,0,3098, + 662,1,0,0,0,3099,3101,3,689,344,0,3100,3099,1,0,0,0,3101,3102,1, + 0,0,0,3102,3100,1,0,0,0,3102,3103,1,0,0,0,3103,3104,1,0,0,0,3104, + 3105,5,76,0,0,3105,664,1,0,0,0,3106,3108,3,689,344,0,3107,3106,1, + 0,0,0,3108,3109,1,0,0,0,3109,3107,1,0,0,0,3109,3110,1,0,0,0,3110, + 3111,1,0,0,0,3111,3112,5,83,0,0,3112,666,1,0,0,0,3113,3115,3,689, + 344,0,3114,3113,1,0,0,0,3115,3116,1,0,0,0,3116,3114,1,0,0,0,3116, + 3117,1,0,0,0,3117,3118,1,0,0,0,3118,3119,5,89,0,0,3119,668,1,0,0, + 0,3120,3122,3,689,344,0,3121,3120,1,0,0,0,3122,3123,1,0,0,0,3123, + 3121,1,0,0,0,3123,3124,1,0,0,0,3124,670,1,0,0,0,3125,3127,3,689, + 344,0,3126,3125,1,0,0,0,3127,3128,1,0,0,0,3128,3126,1,0,0,0,3128, + 3129,1,0,0,0,3129,3130,1,0,0,0,3130,3131,3,687,343,0,3131,3136,1, + 0,0,0,3132,3133,3,685,342,0,3133,3134,3,687,343,0,3134,3136,1,0, + 0,0,3135,3126,1,0,0,0,3135,3132,1,0,0,0,3136,672,1,0,0,0,3137,3138, + 3,685,342,0,3138,674,1,0,0,0,3139,3141,3,689,344,0,3140,3139,1,0, + 0,0,3141,3142,1,0,0,0,3142,3140,1,0,0,0,3142,3143,1,0,0,0,3143,3145, + 1,0,0,0,3144,3146,3,687,343,0,3145,3144,1,0,0,0,3145,3146,1,0,0, + 0,3146,3147,1,0,0,0,3147,3148,5,70,0,0,3148,3156,1,0,0,0,3149,3151, + 3,685,342,0,3150,3152,3,687,343,0,3151,3150,1,0,0,0,3151,3152,1, + 0,0,0,3152,3153,1,0,0,0,3153,3154,5,70,0,0,3154,3156,1,0,0,0,3155, + 3140,1,0,0,0,3155,3149,1,0,0,0,3156,676,1,0,0,0,3157,3159,3,689, + 344,0,3158,3157,1,0,0,0,3159,3160,1,0,0,0,3160,3158,1,0,0,0,3160, + 3161,1,0,0,0,3161,3163,1,0,0,0,3162,3164,3,687,343,0,3163,3162,1, + 0,0,0,3163,3164,1,0,0,0,3164,3165,1,0,0,0,3165,3166,5,68,0,0,3166, + 3174,1,0,0,0,3167,3169,3,685,342,0,3168,3170,3,687,343,0,3169,3168, + 1,0,0,0,3169,3170,1,0,0,0,3170,3171,1,0,0,0,3171,3172,5,68,0,0,3172, + 3174,1,0,0,0,3173,3158,1,0,0,0,3173,3167,1,0,0,0,3174,678,1,0,0, + 0,3175,3177,3,689,344,0,3176,3175,1,0,0,0,3177,3178,1,0,0,0,3178, + 3176,1,0,0,0,3178,3179,1,0,0,0,3179,3181,1,0,0,0,3180,3182,3,687, + 343,0,3181,3180,1,0,0,0,3181,3182,1,0,0,0,3182,3183,1,0,0,0,3183, + 3184,5,66,0,0,3184,3185,5,68,0,0,3185,3194,1,0,0,0,3186,3188,3,685, + 342,0,3187,3189,3,687,343,0,3188,3187,1,0,0,0,3188,3189,1,0,0,0, + 3189,3190,1,0,0,0,3190,3191,5,66,0,0,3191,3192,5,68,0,0,3192,3194, + 1,0,0,0,3193,3176,1,0,0,0,3193,3186,1,0,0,0,3194,680,1,0,0,0,3195, + 3199,3,691,345,0,3196,3199,3,689,344,0,3197,3199,5,95,0,0,3198,3195, + 1,0,0,0,3198,3196,1,0,0,0,3198,3197,1,0,0,0,3199,3200,1,0,0,0,3200, + 3198,1,0,0,0,3200,3201,1,0,0,0,3201,682,1,0,0,0,3202,3208,5,96,0, + 0,3203,3207,8,4,0,0,3204,3205,5,96,0,0,3205,3207,5,96,0,0,3206,3203, + 1,0,0,0,3206,3204,1,0,0,0,3207,3210,1,0,0,0,3208,3206,1,0,0,0,3208, + 3209,1,0,0,0,3209,3211,1,0,0,0,3210,3208,1,0,0,0,3211,3212,5,96, + 0,0,3212,684,1,0,0,0,3213,3215,3,689,344,0,3214,3213,1,0,0,0,3215, + 3216,1,0,0,0,3216,3214,1,0,0,0,3216,3217,1,0,0,0,3217,3218,1,0,0, + 0,3218,3222,5,46,0,0,3219,3221,3,689,344,0,3220,3219,1,0,0,0,3221, + 3224,1,0,0,0,3222,3220,1,0,0,0,3222,3223,1,0,0,0,3223,3232,1,0,0, + 0,3224,3222,1,0,0,0,3225,3227,5,46,0,0,3226,3228,3,689,344,0,3227, + 3226,1,0,0,0,3228,3229,1,0,0,0,3229,3227,1,0,0,0,3229,3230,1,0,0, + 0,3230,3232,1,0,0,0,3231,3214,1,0,0,0,3231,3225,1,0,0,0,3232,686, + 1,0,0,0,3233,3235,5,69,0,0,3234,3236,7,5,0,0,3235,3234,1,0,0,0,3235, + 3236,1,0,0,0,3236,3238,1,0,0,0,3237,3239,3,689,344,0,3238,3237,1, + 0,0,0,3239,3240,1,0,0,0,3240,3238,1,0,0,0,3240,3241,1,0,0,0,3241, + 688,1,0,0,0,3242,3243,7,6,0,0,3243,690,1,0,0,0,3244,3245,7,7,0,0, + 3245,692,1,0,0,0,3246,3247,5,45,0,0,3247,3248,5,45,0,0,3248,3254, + 1,0,0,0,3249,3250,5,92,0,0,3250,3253,5,10,0,0,3251,3253,8,8,0,0, + 3252,3249,1,0,0,0,3252,3251,1,0,0,0,3253,3256,1,0,0,0,3254,3252, + 1,0,0,0,3254,3255,1,0,0,0,3255,3258,1,0,0,0,3256,3254,1,0,0,0,3257, + 3259,5,13,0,0,3258,3257,1,0,0,0,3258,3259,1,0,0,0,3259,3261,1,0, + 0,0,3260,3262,5,10,0,0,3261,3260,1,0,0,0,3261,3262,1,0,0,0,3262, + 3263,1,0,0,0,3263,3264,6,346,0,0,3264,694,1,0,0,0,3265,3266,5,47, + 0,0,3266,3267,5,42,0,0,3267,3272,1,0,0,0,3268,3271,3,695,347,0,3269, + 3271,9,0,0,0,3270,3268,1,0,0,0,3270,3269,1,0,0,0,3271,3274,1,0,0, + 0,3272,3273,1,0,0,0,3272,3270,1,0,0,0,3273,3279,1,0,0,0,3274,3272, + 1,0,0,0,3275,3276,5,42,0,0,3276,3280,5,47,0,0,3277,3278,6,347,1, + 0,3278,3280,5,0,0,1,3279,3275,1,0,0,0,3279,3277,1,0,0,0,3280,3281, + 1,0,0,0,3281,3282,6,347,0,0,3282,696,1,0,0,0,3283,3285,7,9,0,0,3284, + 3283,1,0,0,0,3285,3286,1,0,0,0,3286,3284,1,0,0,0,3286,3287,1,0,0, + 0,3287,3288,1,0,0,0,3288,3289,6,348,0,0,3289,698,1,0,0,0,3290,3291, + 9,0,0,0,3291,700,1,0,0,0,50,0,1935,2347,2671,2995,3013,3021,3060, + 3062,3072,3082,3086,3092,3094,3102,3109,3116,3123,3128,3135,3142, + 3145,3151,3155,3160,3163,3169,3173,3178,3181,3188,3193,3198,3200, + 3206,3208,3216,3222,3229,3231,3235,3240,3252,3254,3258,3261,3270, + 3272,3279,3286,2,0,1,0,1,347,0 + ] + +class SqlBaseLexer(Lexer): + + atn = ATNDeserializer().deserialize(serializedATN()) + + decisionsToDFA = [ DFA(ds, i) for i, ds in enumerate(atn.decisionToState) ] + + SEMICOLON = 1 + LEFT_PAREN = 2 + RIGHT_PAREN = 3 + COMMA = 4 + DOT = 5 + LEFT_BRACKET = 6 + RIGHT_BRACKET = 7 + ADD = 8 + AFTER = 9 + ALL = 10 + ALTER = 11 + ALWAYS = 12 + ANALYZE = 13 + AND = 14 + ANTI = 15 + ANY = 16 + ANY_VALUE = 17 + ARCHIVE = 18 + ARRAY = 19 + AS = 20 + ASC = 21 + AT = 22 + AUTHORIZATION = 23 + BETWEEN = 24 + BOTH = 25 + BUCKET = 26 + BUCKETS = 27 + BY = 28 + CACHE = 29 + CASCADE = 30 + CASE = 31 + CAST = 32 + CATALOG = 33 + CATALOGS = 34 + CHANGE = 35 + CHECK = 36 + CLEAR = 37 + CLUSTER = 38 + CLUSTERED = 39 + CODEGEN = 40 + COLLATE = 41 + COLLECTION = 42 + COLUMN = 43 + COLUMNS = 44 + COMMENT = 45 + COMMIT = 46 + COMPACT = 47 + COMPACTIONS = 48 + COMPUTE = 49 + CONCATENATE = 50 + CONSTRAINT = 51 + COST = 52 + CREATE = 53 + CROSS = 54 + CUBE = 55 + CURRENT = 56 + CURRENT_DATE = 57 + CURRENT_TIME = 58 + CURRENT_TIMESTAMP = 59 + CURRENT_USER = 60 + DAY = 61 + DAYS = 62 + DAYOFYEAR = 63 + DATA = 64 + DATABASE = 65 + DATABASES = 66 + DATEADD = 67 + DATEDIFF = 68 + DBPROPERTIES = 69 + DEFAULT = 70 + DEFINED = 71 + DELETE = 72 + DELIMITED = 73 + DESC = 74 + DESCRIBE = 75 + DFS = 76 + DIRECTORIES = 77 + DIRECTORY = 78 + DISTINCT = 79 + DISTRIBUTE = 80 + DIV = 81 + DROP = 82 + ELSE = 83 + END = 84 + ESCAPE = 85 + ESCAPED = 86 + EXCEPT = 87 + EXCHANGE = 88 + EXCLUDE = 89 + EXISTS = 90 + EXPLAIN = 91 + EXPORT = 92 + EXTENDED = 93 + EXTERNAL = 94 + EXTRACT = 95 + FALSE = 96 + FETCH = 97 + FIELDS = 98 + FILTER = 99 + FILEFORMAT = 100 + FIRST = 101 + FOLLOWING = 102 + FOR = 103 + FOREIGN = 104 + FORMAT = 105 + FORMATTED = 106 + FROM = 107 + FULL = 108 + FUNCTION = 109 + FUNCTIONS = 110 + GENERATED = 111 + GLOBAL = 112 + GRANT = 113 + GROUP = 114 + GROUPING = 115 + HAVING = 116 + HOUR = 117 + HOURS = 118 + IF = 119 + IGNORE = 120 + IMPORT = 121 + IN = 122 + INCLUDE = 123 + INDEX = 124 + INDEXES = 125 + INNER = 126 + INPATH = 127 + INPUTFORMAT = 128 + INSERT = 129 + INTERSECT = 130 + INTERVAL = 131 + INTO = 132 + IS = 133 + ITEMS = 134 + JOIN = 135 + KEYS = 136 + LAST = 137 + LATERAL = 138 + LAZY = 139 + LEADING = 140 + LEFT = 141 + LIKE = 142 + ILIKE = 143 + LIMIT = 144 + LINES = 145 + LIST = 146 + LOAD = 147 + LOCAL = 148 + LOCATION = 149 + LOCK = 150 + LOCKS = 151 + LOGICAL = 152 + MACRO = 153 + MAP = 154 + MATCHED = 155 + MERGE = 156 + MICROSECOND = 157 + MICROSECONDS = 158 + MILLISECOND = 159 + MILLISECONDS = 160 + MINUTE = 161 + MINUTES = 162 + MONTH = 163 + MONTHS = 164 + MSCK = 165 + NAMESPACE = 166 + NAMESPACES = 167 + NANOSECOND = 168 + NANOSECONDS = 169 + NATURAL = 170 + NO = 171 + NOT = 172 + NULL = 173 + NULLS = 174 + OF = 175 + OFFSET = 176 + ON = 177 + ONLY = 178 + OPTION = 179 + OPTIONS = 180 + OR = 181 + ORDER = 182 + OUT = 183 + OUTER = 184 + OUTPUTFORMAT = 185 + OVER = 186 + OVERLAPS = 187 + OVERLAY = 188 + OVERWRITE = 189 + PARTITION = 190 + PARTITIONED = 191 + PARTITIONS = 192 + PERCENTILE_CONT = 193 + PERCENTILE_DISC = 194 + PERCENTLIT = 195 + PIVOT = 196 + PLACING = 197 + POSITION = 198 + PRECEDING = 199 + PRIMARY = 200 + PRINCIPALS = 201 + PROPERTIES = 202 + PURGE = 203 + QUARTER = 204 + QUERY = 205 + RANGE = 206 + RECORDREADER = 207 + RECORDWRITER = 208 + RECOVER = 209 + REDUCE = 210 + REFERENCES = 211 + REFRESH = 212 + RENAME = 213 + REPAIR = 214 + REPEATABLE = 215 + REPLACE = 216 + RESET = 217 + RESPECT = 218 + RESTRICT = 219 + REVOKE = 220 + RIGHT = 221 + RLIKE = 222 + ROLE = 223 + ROLES = 224 + ROLLBACK = 225 + ROLLUP = 226 + ROW = 227 + ROWS = 228 + SECOND = 229 + SECONDS = 230 + SCHEMA = 231 + SCHEMAS = 232 + SELECT = 233 + SEMI = 234 + SEPARATED = 235 + SERDE = 236 + SERDEPROPERTIES = 237 + SESSION_USER = 238 + SET = 239 + SETMINUS = 240 + SETS = 241 + SHOW = 242 + SKEWED = 243 + SOME = 244 + SORT = 245 + SORTED = 246 + SOURCE = 247 + START = 248 + STATISTICS = 249 + STORED = 250 + STRATIFY = 251 + STRUCT = 252 + SUBSTR = 253 + SUBSTRING = 254 + SYNC = 255 + SYSTEM_TIME = 256 + SYSTEM_VERSION = 257 + TABLE = 258 + TABLES = 259 + TABLESAMPLE = 260 + TARGET = 261 + TBLPROPERTIES = 262 + TEMPORARY = 263 + TERMINATED = 264 + THEN = 265 + TIME = 266 + TIMESTAMP = 267 + TIMESTAMPADD = 268 + TIMESTAMPDIFF = 269 + TO = 270 + TOUCH = 271 + TRAILING = 272 + TRANSACTION = 273 + TRANSACTIONS = 274 + TRANSFORM = 275 + TRIM = 276 + TRUE = 277 + TRUNCATE = 278 + TRY_CAST = 279 + TYPE = 280 + UNARCHIVE = 281 + UNBOUNDED = 282 + UNCACHE = 283 + UNION = 284 + UNIQUE = 285 + UNKNOWN = 286 + UNLOCK = 287 + UNPIVOT = 288 + UNSET = 289 + UPDATE = 290 + USE = 291 + USER = 292 + USING = 293 + VALUES = 294 + VERSION = 295 + VIEW = 296 + VIEWS = 297 + WEEK = 298 + WEEKS = 299 + WHEN = 300 + WHERE = 301 + WINDOW = 302 + WITH = 303 + WITHIN = 304 + YEAR = 305 + YEARS = 306 + ZONE = 307 + EQ = 308 + NSEQ = 309 + NEQ = 310 + NEQJ = 311 + LT = 312 + LTE = 313 + GT = 314 + GTE = 315 + PLUS = 316 + MINUS = 317 + ASTERISK = 318 + SLASH = 319 + PERCENT = 320 + TILDE = 321 + AMPERSAND = 322 + PIPE = 323 + CONCAT_PIPE = 324 + HAT = 325 + COLON = 326 + ARROW = 327 + HENT_START = 328 + HENT_END = 329 + STRING = 330 + DOUBLEQUOTED_STRING = 331 + BIGINT_LITERAL = 332 + SMALLINT_LITERAL = 333 + TINYINT_LITERAL = 334 + INTEGER_VALUE = 335 + EXPONENT_VALUE = 336 + DECIMAL_VALUE = 337 + FLOAT_LITERAL = 338 + DOUBLE_LITERAL = 339 + BIGDECIMAL_LITERAL = 340 + IDENTIFIER = 341 + BACKQUOTED_IDENTIFIER = 342 + SIMPLE_COMMENT = 343 + BRACKETED_COMMENT = 344 + WS = 345 + UNRECOGNIZED = 346 + + channelNames = [ u"DEFAULT_TOKEN_CHANNEL", u"HIDDEN" ] + + modeNames = [ "DEFAULT_MODE" ] + + literalNames = [ "", + "';'", "'('", "')'", "','", "'.'", "'['", "']'", "'ADD'", "'AFTER'", + "'ALL'", "'ALTER'", "'ALWAYS'", "'ANALYZE'", "'AND'", "'ANTI'", + "'ANY'", "'ANY_VALUE'", "'ARCHIVE'", "'ARRAY'", "'AS'", "'ASC'", + "'AT'", "'AUTHORIZATION'", "'BETWEEN'", "'BOTH'", "'BUCKET'", + "'BUCKETS'", "'BY'", "'CACHE'", "'CASCADE'", "'CASE'", "'CAST'", + "'CATALOG'", "'CATALOGS'", "'CHANGE'", "'CHECK'", "'CLEAR'", + "'CLUSTER'", "'CLUSTERED'", "'CODEGEN'", "'COLLATE'", "'COLLECTION'", + "'COLUMN'", "'COLUMNS'", "'COMMENT'", "'COMMIT'", "'COMPACT'", + "'COMPACTIONS'", "'COMPUTE'", "'CONCATENATE'", "'CONSTRAINT'", + "'COST'", "'CREATE'", "'CROSS'", "'CUBE'", "'CURRENT'", "'CURRENT_DATE'", + "'CURRENT_TIME'", "'CURRENT_TIMESTAMP'", "'CURRENT_USER'", "'DAY'", + "'DAYS'", "'DAYOFYEAR'", "'DATA'", "'DATABASE'", "'DATABASES'", + "'DATEADD'", "'DATEDIFF'", "'DBPROPERTIES'", "'DEFAULT'", "'DEFINED'", + "'DELETE'", "'DELIMITED'", "'DESC'", "'DESCRIBE'", "'DFS'", + "'DIRECTORIES'", "'DIRECTORY'", "'DISTINCT'", "'DISTRIBUTE'", + "'DIV'", "'DROP'", "'ELSE'", "'END'", "'ESCAPE'", "'ESCAPED'", + "'EXCEPT'", "'EXCHANGE'", "'EXCLUDE'", "'EXISTS'", "'EXPLAIN'", + "'EXPORT'", "'EXTENDED'", "'EXTERNAL'", "'EXTRACT'", "'FALSE'", + "'FETCH'", "'FIELDS'", "'FILTER'", "'FILEFORMAT'", "'FIRST'", + "'FOLLOWING'", "'FOR'", "'FOREIGN'", "'FORMAT'", "'FORMATTED'", + "'FROM'", "'FULL'", "'FUNCTION'", "'FUNCTIONS'", "'GENERATED'", + "'GLOBAL'", "'GRANT'", "'GROUP'", "'GROUPING'", "'HAVING'", + "'HOUR'", "'HOURS'", "'IF'", "'IGNORE'", "'IMPORT'", "'IN'", + "'INCLUDE'", "'INDEX'", "'INDEXES'", "'INNER'", "'INPATH'", + "'INPUTFORMAT'", "'INSERT'", "'INTERSECT'", "'INTERVAL'", "'INTO'", + "'IS'", "'ITEMS'", "'JOIN'", "'KEYS'", "'LAST'", "'LATERAL'", + "'LAZY'", "'LEADING'", "'LEFT'", "'LIKE'", "'ILIKE'", "'LIMIT'", + "'LINES'", "'LIST'", "'LOAD'", "'LOCAL'", "'LOCATION'", "'LOCK'", + "'LOCKS'", "'LOGICAL'", "'MACRO'", "'MAP'", "'MATCHED'", "'MERGE'", + "'MICROSECOND'", "'MICROSECONDS'", "'MILLISECOND'", "'MILLISECONDS'", + "'MINUTE'", "'MINUTES'", "'MONTH'", "'MONTHS'", "'MSCK'", "'NAMESPACE'", + "'NAMESPACES'", "'NANOSECOND'", "'NANOSECONDS'", "'NATURAL'", + "'NO'", "'NULL'", "'NULLS'", "'OF'", "'OFFSET'", "'ON'", "'ONLY'", + "'OPTION'", "'OPTIONS'", "'OR'", "'ORDER'", "'OUT'", "'OUTER'", + "'OUTPUTFORMAT'", "'OVER'", "'OVERLAPS'", "'OVERLAY'", "'OVERWRITE'", + "'PARTITION'", "'PARTITIONED'", "'PARTITIONS'", "'PERCENTILE_CONT'", + "'PERCENTILE_DISC'", "'PERCENT'", "'PIVOT'", "'PLACING'", "'POSITION'", + "'PRECEDING'", "'PRIMARY'", "'PRINCIPALS'", "'PROPERTIES'", + "'PURGE'", "'QUARTER'", "'QUERY'", "'RANGE'", "'RECORDREADER'", + "'RECORDWRITER'", "'RECOVER'", "'REDUCE'", "'REFERENCES'", "'REFRESH'", + "'RENAME'", "'REPAIR'", "'REPEATABLE'", "'REPLACE'", "'RESET'", + "'RESPECT'", "'RESTRICT'", "'REVOKE'", "'RIGHT'", "'ROLE'", + "'ROLES'", "'ROLLBACK'", "'ROLLUP'", "'ROW'", "'ROWS'", "'SECOND'", + "'SECONDS'", "'SCHEMA'", "'SCHEMAS'", "'SELECT'", "'SEMI'", + "'SEPARATED'", "'SERDE'", "'SERDEPROPERTIES'", "'SESSION_USER'", + "'SET'", "'MINUS'", "'SETS'", "'SHOW'", "'SKEWED'", "'SOME'", + "'SORT'", "'SORTED'", "'SOURCE'", "'START'", "'STATISTICS'", + "'STORED'", "'STRATIFY'", "'STRUCT'", "'SUBSTR'", "'SUBSTRING'", + "'SYNC'", "'SYSTEM_TIME'", "'SYSTEM_VERSION'", "'TABLE'", "'TABLES'", + "'TABLESAMPLE'", "'TARGET'", "'TBLPROPERTIES'", "'TERMINATED'", + "'THEN'", "'TIME'", "'TIMESTAMP'", "'TIMESTAMPADD'", "'TIMESTAMPDIFF'", + "'TO'", "'TOUCH'", "'TRAILING'", "'TRANSACTION'", "'TRANSACTIONS'", + "'TRANSFORM'", "'TRIM'", "'TRUE'", "'TRUNCATE'", "'TRY_CAST'", + "'TYPE'", "'UNARCHIVE'", "'UNBOUNDED'", "'UNCACHE'", "'UNION'", + "'UNIQUE'", "'UNKNOWN'", "'UNLOCK'", "'UNPIVOT'", "'UNSET'", + "'UPDATE'", "'USE'", "'USER'", "'USING'", "'VALUES'", "'VERSION'", + "'VIEW'", "'VIEWS'", "'WEEK'", "'WEEKS'", "'WHEN'", "'WHERE'", + "'WINDOW'", "'WITH'", "'WITHIN'", "'YEAR'", "'YEARS'", "'ZONE'", + "'<=>'", "'<>'", "'!='", "'<'", "'>'", "'+'", "'-'", "'*'", + "'/'", "'%'", "'~'", "'&'", "'|'", "'||'", "'^'", "':'", "'->'", + "'/*+'", "'*/'" ] + + symbolicNames = [ "", + "SEMICOLON", "LEFT_PAREN", "RIGHT_PAREN", "COMMA", "DOT", "LEFT_BRACKET", + "RIGHT_BRACKET", "ADD", "AFTER", "ALL", "ALTER", "ALWAYS", "ANALYZE", + "AND", "ANTI", "ANY", "ANY_VALUE", "ARCHIVE", "ARRAY", "AS", + "ASC", "AT", "AUTHORIZATION", "BETWEEN", "BOTH", "BUCKET", "BUCKETS", + "BY", "CACHE", "CASCADE", "CASE", "CAST", "CATALOG", "CATALOGS", + "CHANGE", "CHECK", "CLEAR", "CLUSTER", "CLUSTERED", "CODEGEN", + "COLLATE", "COLLECTION", "COLUMN", "COLUMNS", "COMMENT", "COMMIT", + "COMPACT", "COMPACTIONS", "COMPUTE", "CONCATENATE", "CONSTRAINT", + "COST", "CREATE", "CROSS", "CUBE", "CURRENT", "CURRENT_DATE", + "CURRENT_TIME", "CURRENT_TIMESTAMP", "CURRENT_USER", "DAY", + "DAYS", "DAYOFYEAR", "DATA", "DATABASE", "DATABASES", "DATEADD", + "DATEDIFF", "DBPROPERTIES", "DEFAULT", "DEFINED", "DELETE", + "DELIMITED", "DESC", "DESCRIBE", "DFS", "DIRECTORIES", "DIRECTORY", + "DISTINCT", "DISTRIBUTE", "DIV", "DROP", "ELSE", "END", "ESCAPE", + "ESCAPED", "EXCEPT", "EXCHANGE", "EXCLUDE", "EXISTS", "EXPLAIN", + "EXPORT", "EXTENDED", "EXTERNAL", "EXTRACT", "FALSE", "FETCH", + "FIELDS", "FILTER", "FILEFORMAT", "FIRST", "FOLLOWING", "FOR", + "FOREIGN", "FORMAT", "FORMATTED", "FROM", "FULL", "FUNCTION", + "FUNCTIONS", "GENERATED", "GLOBAL", "GRANT", "GROUP", "GROUPING", + "HAVING", "HOUR", "HOURS", "IF", "IGNORE", "IMPORT", "IN", "INCLUDE", + "INDEX", "INDEXES", "INNER", "INPATH", "INPUTFORMAT", "INSERT", + "INTERSECT", "INTERVAL", "INTO", "IS", "ITEMS", "JOIN", "KEYS", + "LAST", "LATERAL", "LAZY", "LEADING", "LEFT", "LIKE", "ILIKE", + "LIMIT", "LINES", "LIST", "LOAD", "LOCAL", "LOCATION", "LOCK", + "LOCKS", "LOGICAL", "MACRO", "MAP", "MATCHED", "MERGE", "MICROSECOND", + "MICROSECONDS", "MILLISECOND", "MILLISECONDS", "MINUTE", "MINUTES", + "MONTH", "MONTHS", "MSCK", "NAMESPACE", "NAMESPACES", "NANOSECOND", + "NANOSECONDS", "NATURAL", "NO", "NOT", "NULL", "NULLS", "OF", + "OFFSET", "ON", "ONLY", "OPTION", "OPTIONS", "OR", "ORDER", + "OUT", "OUTER", "OUTPUTFORMAT", "OVER", "OVERLAPS", "OVERLAY", + "OVERWRITE", "PARTITION", "PARTITIONED", "PARTITIONS", "PERCENTILE_CONT", + "PERCENTILE_DISC", "PERCENTLIT", "PIVOT", "PLACING", "POSITION", + "PRECEDING", "PRIMARY", "PRINCIPALS", "PROPERTIES", "PURGE", + "QUARTER", "QUERY", "RANGE", "RECORDREADER", "RECORDWRITER", + "RECOVER", "REDUCE", "REFERENCES", "REFRESH", "RENAME", "REPAIR", + "REPEATABLE", "REPLACE", "RESET", "RESPECT", "RESTRICT", "REVOKE", + "RIGHT", "RLIKE", "ROLE", "ROLES", "ROLLBACK", "ROLLUP", "ROW", + "ROWS", "SECOND", "SECONDS", "SCHEMA", "SCHEMAS", "SELECT", + "SEMI", "SEPARATED", "SERDE", "SERDEPROPERTIES", "SESSION_USER", + "SET", "SETMINUS", "SETS", "SHOW", "SKEWED", "SOME", "SORT", + "SORTED", "SOURCE", "START", "STATISTICS", "STORED", "STRATIFY", + "STRUCT", "SUBSTR", "SUBSTRING", "SYNC", "SYSTEM_TIME", "SYSTEM_VERSION", + "TABLE", "TABLES", "TABLESAMPLE", "TARGET", "TBLPROPERTIES", + "TEMPORARY", "TERMINATED", "THEN", "TIME", "TIMESTAMP", "TIMESTAMPADD", + "TIMESTAMPDIFF", "TO", "TOUCH", "TRAILING", "TRANSACTION", "TRANSACTIONS", + "TRANSFORM", "TRIM", "TRUE", "TRUNCATE", "TRY_CAST", "TYPE", + "UNARCHIVE", "UNBOUNDED", "UNCACHE", "UNION", "UNIQUE", "UNKNOWN", + "UNLOCK", "UNPIVOT", "UNSET", "UPDATE", "USE", "USER", "USING", + "VALUES", "VERSION", "VIEW", "VIEWS", "WEEK", "WEEKS", "WHEN", + "WHERE", "WINDOW", "WITH", "WITHIN", "YEAR", "YEARS", "ZONE", + "EQ", "NSEQ", "NEQ", "NEQJ", "LT", "LTE", "GT", "GTE", "PLUS", + "MINUS", "ASTERISK", "SLASH", "PERCENT", "TILDE", "AMPERSAND", + "PIPE", "CONCAT_PIPE", "HAT", "COLON", "ARROW", "HENT_START", + "HENT_END", "STRING", "DOUBLEQUOTED_STRING", "BIGINT_LITERAL", + "SMALLINT_LITERAL", "TINYINT_LITERAL", "INTEGER_VALUE", "EXPONENT_VALUE", + "DECIMAL_VALUE", "FLOAT_LITERAL", "DOUBLE_LITERAL", "BIGDECIMAL_LITERAL", + "IDENTIFIER", "BACKQUOTED_IDENTIFIER", "SIMPLE_COMMENT", "BRACKETED_COMMENT", + "WS", "UNRECOGNIZED" ] + + ruleNames = [ "SEMICOLON", "LEFT_PAREN", "RIGHT_PAREN", "COMMA", "DOT", + "LEFT_BRACKET", "RIGHT_BRACKET", "ADD", "AFTER", "ALL", + "ALTER", "ALWAYS", "ANALYZE", "AND", "ANTI", "ANY", "ANY_VALUE", + "ARCHIVE", "ARRAY", "AS", "ASC", "AT", "AUTHORIZATION", + "BETWEEN", "BOTH", "BUCKET", "BUCKETS", "BY", "CACHE", + "CASCADE", "CASE", "CAST", "CATALOG", "CATALOGS", "CHANGE", + "CHECK", "CLEAR", "CLUSTER", "CLUSTERED", "CODEGEN", "COLLATE", + "COLLECTION", "COLUMN", "COLUMNS", "COMMENT", "COMMIT", + "COMPACT", "COMPACTIONS", "COMPUTE", "CONCATENATE", "CONSTRAINT", + "COST", "CREATE", "CROSS", "CUBE", "CURRENT", "CURRENT_DATE", + "CURRENT_TIME", "CURRENT_TIMESTAMP", "CURRENT_USER", "DAY", + "DAYS", "DAYOFYEAR", "DATA", "DATABASE", "DATABASES", + "DATEADD", "DATEDIFF", "DBPROPERTIES", "DEFAULT", "DEFINED", + "DELETE", "DELIMITED", "DESC", "DESCRIBE", "DFS", "DIRECTORIES", + "DIRECTORY", "DISTINCT", "DISTRIBUTE", "DIV", "DROP", + "ELSE", "END", "ESCAPE", "ESCAPED", "EXCEPT", "EXCHANGE", + "EXCLUDE", "EXISTS", "EXPLAIN", "EXPORT", "EXTENDED", + "EXTERNAL", "EXTRACT", "FALSE", "FETCH", "FIELDS", "FILTER", + "FILEFORMAT", "FIRST", "FOLLOWING", "FOR", "FOREIGN", + "FORMAT", "FORMATTED", "FROM", "FULL", "FUNCTION", "FUNCTIONS", + "GENERATED", "GLOBAL", "GRANT", "GROUP", "GROUPING", "HAVING", + "HOUR", "HOURS", "IF", "IGNORE", "IMPORT", "IN", "INCLUDE", + "INDEX", "INDEXES", "INNER", "INPATH", "INPUTFORMAT", + "INSERT", "INTERSECT", "INTERVAL", "INTO", "IS", "ITEMS", + "JOIN", "KEYS", "LAST", "LATERAL", "LAZY", "LEADING", + "LEFT", "LIKE", "ILIKE", "LIMIT", "LINES", "LIST", "LOAD", + "LOCAL", "LOCATION", "LOCK", "LOCKS", "LOGICAL", "MACRO", + "MAP", "MATCHED", "MERGE", "MICROSECOND", "MICROSECONDS", + "MILLISECOND", "MILLISECONDS", "MINUTE", "MINUTES", "MONTH", + "MONTHS", "MSCK", "NAMESPACE", "NAMESPACES", "NANOSECOND", + "NANOSECONDS", "NATURAL", "NO", "NOT", "NULL", "NULLS", + "OF", "OFFSET", "ON", "ONLY", "OPTION", "OPTIONS", "OR", + "ORDER", "OUT", "OUTER", "OUTPUTFORMAT", "OVER", "OVERLAPS", + "OVERLAY", "OVERWRITE", "PARTITION", "PARTITIONED", "PARTITIONS", + "PERCENTILE_CONT", "PERCENTILE_DISC", "PERCENTLIT", "PIVOT", + "PLACING", "POSITION", "PRECEDING", "PRIMARY", "PRINCIPALS", + "PROPERTIES", "PURGE", "QUARTER", "QUERY", "RANGE", "RECORDREADER", + "RECORDWRITER", "RECOVER", "REDUCE", "REFERENCES", "REFRESH", + "RENAME", "REPAIR", "REPEATABLE", "REPLACE", "RESET", + "RESPECT", "RESTRICT", "REVOKE", "RIGHT", "RLIKE", "ROLE", + "ROLES", "ROLLBACK", "ROLLUP", "ROW", "ROWS", "SECOND", + "SECONDS", "SCHEMA", "SCHEMAS", "SELECT", "SEMI", "SEPARATED", + "SERDE", "SERDEPROPERTIES", "SESSION_USER", "SET", "SETMINUS", + "SETS", "SHOW", "SKEWED", "SOME", "SORT", "SORTED", "SOURCE", + "START", "STATISTICS", "STORED", "STRATIFY", "STRUCT", + "SUBSTR", "SUBSTRING", "SYNC", "SYSTEM_TIME", "SYSTEM_VERSION", + "TABLE", "TABLES", "TABLESAMPLE", "TARGET", "TBLPROPERTIES", + "TEMPORARY", "TERMINATED", "THEN", "TIME", "TIMESTAMP", + "TIMESTAMPADD", "TIMESTAMPDIFF", "TO", "TOUCH", "TRAILING", + "TRANSACTION", "TRANSACTIONS", "TRANSFORM", "TRIM", "TRUE", + "TRUNCATE", "TRY_CAST", "TYPE", "UNARCHIVE", "UNBOUNDED", + "UNCACHE", "UNION", "UNIQUE", "UNKNOWN", "UNLOCK", "UNPIVOT", + "UNSET", "UPDATE", "USE", "USER", "USING", "VALUES", "VERSION", + "VIEW", "VIEWS", "WEEK", "WEEKS", "WHEN", "WHERE", "WINDOW", + "WITH", "WITHIN", "YEAR", "YEARS", "ZONE", "EQ", "NSEQ", + "NEQ", "NEQJ", "LT", "LTE", "GT", "GTE", "PLUS", "MINUS", + "ASTERISK", "SLASH", "PERCENT", "TILDE", "AMPERSAND", + "PIPE", "CONCAT_PIPE", "HAT", "COLON", "ARROW", "HENT_START", + "HENT_END", "STRING", "DOUBLEQUOTED_STRING", "BIGINT_LITERAL", + "SMALLINT_LITERAL", "TINYINT_LITERAL", "INTEGER_VALUE", + "EXPONENT_VALUE", "DECIMAL_VALUE", "FLOAT_LITERAL", "DOUBLE_LITERAL", + "BIGDECIMAL_LITERAL", "IDENTIFIER", "BACKQUOTED_IDENTIFIER", + "DECIMAL_DIGITS", "EXPONENT", "DIGIT", "LETTER", "SIMPLE_COMMENT", + "BRACKETED_COMMENT", "WS", "UNRECOGNIZED" ] + + grammarFileName = "SqlBaseLexer.g4" + + def __init__(self, input=None, output:TextIO = sys.stdout): + super().__init__(input, output) + self.checkVersion("4.12.0") + self._interp = LexerATNSimulator(self, self.atn, self.decisionsToDFA, PredictionContextCache()) + self._actions = None + self._predicates = None + + + def action(self, localctx:RuleContext, ruleIndex:int, actionIndex:int): + if self._actions is None: + actions = dict() + actions[347] = self.BRACKETED_COMMENT_action + self._actions = actions + action = self._actions.get(ruleIndex, None) + if action is not None: + action(localctx, actionIndex) + else: + raise Exception("No registered action for:" + str(ruleIndex)) + + + def BRACKETED_COMMENT_action(self, localctx:RuleContext , actionIndex:int): + if actionIndex == 0: + markUnclosedComment(); + + + diff --git a/datajunction-server/datajunction_server/sql/parsing/backends/grammar/generated/SqlBaseLexer.tokens b/datajunction-server/datajunction_server/sql/parsing/backends/grammar/generated/SqlBaseLexer.tokens new file mode 100644 index 000000000..c76d66e32 --- /dev/null +++ b/datajunction-server/datajunction_server/sql/parsing/backends/grammar/generated/SqlBaseLexer.tokens @@ -0,0 +1,669 @@ +SEMICOLON=1 +LEFT_PAREN=2 +RIGHT_PAREN=3 +COMMA=4 +DOT=5 +LEFT_BRACKET=6 +RIGHT_BRACKET=7 +ADD=8 +AFTER=9 +ALL=10 +ALTER=11 +ALWAYS=12 +ANALYZE=13 +AND=14 +ANTI=15 +ANY=16 +ANY_VALUE=17 +ARCHIVE=18 +ARRAY=19 +AS=20 +ASC=21 +AT=22 +AUTHORIZATION=23 +BETWEEN=24 +BOTH=25 +BUCKET=26 +BUCKETS=27 +BY=28 +CACHE=29 +CASCADE=30 +CASE=31 +CAST=32 +CATALOG=33 +CATALOGS=34 +CHANGE=35 +CHECK=36 +CLEAR=37 +CLUSTER=38 +CLUSTERED=39 +CODEGEN=40 +COLLATE=41 +COLLECTION=42 +COLUMN=43 +COLUMNS=44 +COMMENT=45 +COMMIT=46 +COMPACT=47 +COMPACTIONS=48 +COMPUTE=49 +CONCATENATE=50 +CONSTRAINT=51 +COST=52 +CREATE=53 +CROSS=54 +CUBE=55 +CURRENT=56 +CURRENT_DATE=57 +CURRENT_TIME=58 +CURRENT_TIMESTAMP=59 +CURRENT_USER=60 +DAY=61 +DAYS=62 +DAYOFYEAR=63 +DATA=64 +DATABASE=65 +DATABASES=66 +DATEADD=67 +DATEDIFF=68 +DBPROPERTIES=69 +DEFAULT=70 +DEFINED=71 +DELETE=72 +DELIMITED=73 +DESC=74 +DESCRIBE=75 +DFS=76 +DIRECTORIES=77 +DIRECTORY=78 +DISTINCT=79 +DISTRIBUTE=80 +DIV=81 +DROP=82 +ELSE=83 +END=84 +ESCAPE=85 +ESCAPED=86 +EXCEPT=87 +EXCHANGE=88 +EXCLUDE=89 +EXISTS=90 +EXPLAIN=91 +EXPORT=92 +EXTENDED=93 +EXTERNAL=94 +EXTRACT=95 +FALSE=96 +FETCH=97 +FIELDS=98 +FILTER=99 +FILEFORMAT=100 +FIRST=101 +FOLLOWING=102 +FOR=103 +FOREIGN=104 +FORMAT=105 +FORMATTED=106 +FROM=107 +FULL=108 +FUNCTION=109 +FUNCTIONS=110 +GENERATED=111 +GLOBAL=112 +GRANT=113 +GROUP=114 +GROUPING=115 +HAVING=116 +HOUR=117 +HOURS=118 +IF=119 +IGNORE=120 +IMPORT=121 +IN=122 +INCLUDE=123 +INDEX=124 +INDEXES=125 +INNER=126 +INPATH=127 +INPUTFORMAT=128 +INSERT=129 +INTERSECT=130 +INTERVAL=131 +INTO=132 +IS=133 +ITEMS=134 +JOIN=135 +KEYS=136 +LAST=137 +LATERAL=138 +LAZY=139 +LEADING=140 +LEFT=141 +LIKE=142 +ILIKE=143 +LIMIT=144 +LINES=145 +LIST=146 +LOAD=147 +LOCAL=148 +LOCATION=149 +LOCK=150 +LOCKS=151 +LOGICAL=152 +MACRO=153 +MAP=154 +MATCHED=155 +MERGE=156 +MICROSECOND=157 +MICROSECONDS=158 +MILLISECOND=159 +MILLISECONDS=160 +MINUTE=161 +MINUTES=162 +MONTH=163 +MONTHS=164 +MSCK=165 +NAMESPACE=166 +NAMESPACES=167 +NANOSECOND=168 +NANOSECONDS=169 +NATURAL=170 +NO=171 +NOT=172 +NULL=173 +NULLS=174 +OF=175 +OFFSET=176 +ON=177 +ONLY=178 +OPTION=179 +OPTIONS=180 +OR=181 +ORDER=182 +OUT=183 +OUTER=184 +OUTPUTFORMAT=185 +OVER=186 +OVERLAPS=187 +OVERLAY=188 +OVERWRITE=189 +PARTITION=190 +PARTITIONED=191 +PARTITIONS=192 +PERCENTILE_CONT=193 +PERCENTILE_DISC=194 +PERCENTLIT=195 +PIVOT=196 +PLACING=197 +POSITION=198 +PRECEDING=199 +PRIMARY=200 +PRINCIPALS=201 +PROPERTIES=202 +PURGE=203 +QUARTER=204 +QUERY=205 +RANGE=206 +RECORDREADER=207 +RECORDWRITER=208 +RECOVER=209 +REDUCE=210 +REFERENCES=211 +REFRESH=212 +RENAME=213 +REPAIR=214 +REPEATABLE=215 +REPLACE=216 +RESET=217 +RESPECT=218 +RESTRICT=219 +REVOKE=220 +RIGHT=221 +RLIKE=222 +ROLE=223 +ROLES=224 +ROLLBACK=225 +ROLLUP=226 +ROW=227 +ROWS=228 +SECOND=229 +SECONDS=230 +SCHEMA=231 +SCHEMAS=232 +SELECT=233 +SEMI=234 +SEPARATED=235 +SERDE=236 +SERDEPROPERTIES=237 +SESSION_USER=238 +SET=239 +SETMINUS=240 +SETS=241 +SHOW=242 +SKEWED=243 +SOME=244 +SORT=245 +SORTED=246 +SOURCE=247 +START=248 +STATISTICS=249 +STORED=250 +STRATIFY=251 +STRUCT=252 +SUBSTR=253 +SUBSTRING=254 +SYNC=255 +SYSTEM_TIME=256 +SYSTEM_VERSION=257 +TABLE=258 +TABLES=259 +TABLESAMPLE=260 +TARGET=261 +TBLPROPERTIES=262 +TEMPORARY=263 +TERMINATED=264 +THEN=265 +TIME=266 +TIMESTAMP=267 +TIMESTAMPADD=268 +TIMESTAMPDIFF=269 +TO=270 +TOUCH=271 +TRAILING=272 +TRANSACTION=273 +TRANSACTIONS=274 +TRANSFORM=275 +TRIM=276 +TRUE=277 +TRUNCATE=278 +TRY_CAST=279 +TYPE=280 +UNARCHIVE=281 +UNBOUNDED=282 +UNCACHE=283 +UNION=284 +UNIQUE=285 +UNKNOWN=286 +UNLOCK=287 +UNPIVOT=288 +UNSET=289 +UPDATE=290 +USE=291 +USER=292 +USING=293 +VALUES=294 +VERSION=295 +VIEW=296 +VIEWS=297 +WEEK=298 +WEEKS=299 +WHEN=300 +WHERE=301 +WINDOW=302 +WITH=303 +WITHIN=304 +YEAR=305 +YEARS=306 +ZONE=307 +EQ=308 +NSEQ=309 +NEQ=310 +NEQJ=311 +LT=312 +LTE=313 +GT=314 +GTE=315 +PLUS=316 +MINUS=317 +ASTERISK=318 +SLASH=319 +PERCENT=320 +TILDE=321 +AMPERSAND=322 +PIPE=323 +CONCAT_PIPE=324 +HAT=325 +COLON=326 +ARROW=327 +HENT_START=328 +HENT_END=329 +STRING=330 +DOUBLEQUOTED_STRING=331 +BIGINT_LITERAL=332 +SMALLINT_LITERAL=333 +TINYINT_LITERAL=334 +INTEGER_VALUE=335 +EXPONENT_VALUE=336 +DECIMAL_VALUE=337 +FLOAT_LITERAL=338 +DOUBLE_LITERAL=339 +BIGDECIMAL_LITERAL=340 +IDENTIFIER=341 +BACKQUOTED_IDENTIFIER=342 +SIMPLE_COMMENT=343 +BRACKETED_COMMENT=344 +WS=345 +UNRECOGNIZED=346 +';'=1 +'('=2 +')'=3 +','=4 +'.'=5 +'['=6 +']'=7 +'ADD'=8 +'AFTER'=9 +'ALL'=10 +'ALTER'=11 +'ALWAYS'=12 +'ANALYZE'=13 +'AND'=14 +'ANTI'=15 +'ANY'=16 +'ANY_VALUE'=17 +'ARCHIVE'=18 +'ARRAY'=19 +'AS'=20 +'ASC'=21 +'AT'=22 +'AUTHORIZATION'=23 +'BETWEEN'=24 +'BOTH'=25 +'BUCKET'=26 +'BUCKETS'=27 +'BY'=28 +'CACHE'=29 +'CASCADE'=30 +'CASE'=31 +'CAST'=32 +'CATALOG'=33 +'CATALOGS'=34 +'CHANGE'=35 +'CHECK'=36 +'CLEAR'=37 +'CLUSTER'=38 +'CLUSTERED'=39 +'CODEGEN'=40 +'COLLATE'=41 +'COLLECTION'=42 +'COLUMN'=43 +'COLUMNS'=44 +'COMMENT'=45 +'COMMIT'=46 +'COMPACT'=47 +'COMPACTIONS'=48 +'COMPUTE'=49 +'CONCATENATE'=50 +'CONSTRAINT'=51 +'COST'=52 +'CREATE'=53 +'CROSS'=54 +'CUBE'=55 +'CURRENT'=56 +'CURRENT_DATE'=57 +'CURRENT_TIME'=58 +'CURRENT_TIMESTAMP'=59 +'CURRENT_USER'=60 +'DAY'=61 +'DAYS'=62 +'DAYOFYEAR'=63 +'DATA'=64 +'DATABASE'=65 +'DATABASES'=66 +'DATEADD'=67 +'DATEDIFF'=68 +'DBPROPERTIES'=69 +'DEFAULT'=70 +'DEFINED'=71 +'DELETE'=72 +'DELIMITED'=73 +'DESC'=74 +'DESCRIBE'=75 +'DFS'=76 +'DIRECTORIES'=77 +'DIRECTORY'=78 +'DISTINCT'=79 +'DISTRIBUTE'=80 +'DIV'=81 +'DROP'=82 +'ELSE'=83 +'END'=84 +'ESCAPE'=85 +'ESCAPED'=86 +'EXCEPT'=87 +'EXCHANGE'=88 +'EXCLUDE'=89 +'EXISTS'=90 +'EXPLAIN'=91 +'EXPORT'=92 +'EXTENDED'=93 +'EXTERNAL'=94 +'EXTRACT'=95 +'FALSE'=96 +'FETCH'=97 +'FIELDS'=98 +'FILTER'=99 +'FILEFORMAT'=100 +'FIRST'=101 +'FOLLOWING'=102 +'FOR'=103 +'FOREIGN'=104 +'FORMAT'=105 +'FORMATTED'=106 +'FROM'=107 +'FULL'=108 +'FUNCTION'=109 +'FUNCTIONS'=110 +'GENERATED'=111 +'GLOBAL'=112 +'GRANT'=113 +'GROUP'=114 +'GROUPING'=115 +'HAVING'=116 +'HOUR'=117 +'HOURS'=118 +'IF'=119 +'IGNORE'=120 +'IMPORT'=121 +'IN'=122 +'INCLUDE'=123 +'INDEX'=124 +'INDEXES'=125 +'INNER'=126 +'INPATH'=127 +'INPUTFORMAT'=128 +'INSERT'=129 +'INTERSECT'=130 +'INTERVAL'=131 +'INTO'=132 +'IS'=133 +'ITEMS'=134 +'JOIN'=135 +'KEYS'=136 +'LAST'=137 +'LATERAL'=138 +'LAZY'=139 +'LEADING'=140 +'LEFT'=141 +'LIKE'=142 +'ILIKE'=143 +'LIMIT'=144 +'LINES'=145 +'LIST'=146 +'LOAD'=147 +'LOCAL'=148 +'LOCATION'=149 +'LOCK'=150 +'LOCKS'=151 +'LOGICAL'=152 +'MACRO'=153 +'MAP'=154 +'MATCHED'=155 +'MERGE'=156 +'MICROSECOND'=157 +'MICROSECONDS'=158 +'MILLISECOND'=159 +'MILLISECONDS'=160 +'MINUTE'=161 +'MINUTES'=162 +'MONTH'=163 +'MONTHS'=164 +'MSCK'=165 +'NAMESPACE'=166 +'NAMESPACES'=167 +'NANOSECOND'=168 +'NANOSECONDS'=169 +'NATURAL'=170 +'NO'=171 +'NULL'=173 +'NULLS'=174 +'OF'=175 +'OFFSET'=176 +'ON'=177 +'ONLY'=178 +'OPTION'=179 +'OPTIONS'=180 +'OR'=181 +'ORDER'=182 +'OUT'=183 +'OUTER'=184 +'OUTPUTFORMAT'=185 +'OVER'=186 +'OVERLAPS'=187 +'OVERLAY'=188 +'OVERWRITE'=189 +'PARTITION'=190 +'PARTITIONED'=191 +'PARTITIONS'=192 +'PERCENTILE_CONT'=193 +'PERCENTILE_DISC'=194 +'PERCENT'=195 +'PIVOT'=196 +'PLACING'=197 +'POSITION'=198 +'PRECEDING'=199 +'PRIMARY'=200 +'PRINCIPALS'=201 +'PROPERTIES'=202 +'PURGE'=203 +'QUARTER'=204 +'QUERY'=205 +'RANGE'=206 +'RECORDREADER'=207 +'RECORDWRITER'=208 +'RECOVER'=209 +'REDUCE'=210 +'REFERENCES'=211 +'REFRESH'=212 +'RENAME'=213 +'REPAIR'=214 +'REPEATABLE'=215 +'REPLACE'=216 +'RESET'=217 +'RESPECT'=218 +'RESTRICT'=219 +'REVOKE'=220 +'RIGHT'=221 +'ROLE'=223 +'ROLES'=224 +'ROLLBACK'=225 +'ROLLUP'=226 +'ROW'=227 +'ROWS'=228 +'SECOND'=229 +'SECONDS'=230 +'SCHEMA'=231 +'SCHEMAS'=232 +'SELECT'=233 +'SEMI'=234 +'SEPARATED'=235 +'SERDE'=236 +'SERDEPROPERTIES'=237 +'SESSION_USER'=238 +'SET'=239 +'MINUS'=240 +'SETS'=241 +'SHOW'=242 +'SKEWED'=243 +'SOME'=244 +'SORT'=245 +'SORTED'=246 +'SOURCE'=247 +'START'=248 +'STATISTICS'=249 +'STORED'=250 +'STRATIFY'=251 +'STRUCT'=252 +'SUBSTR'=253 +'SUBSTRING'=254 +'SYNC'=255 +'SYSTEM_TIME'=256 +'SYSTEM_VERSION'=257 +'TABLE'=258 +'TABLES'=259 +'TABLESAMPLE'=260 +'TARGET'=261 +'TBLPROPERTIES'=262 +'TERMINATED'=264 +'THEN'=265 +'TIME'=266 +'TIMESTAMP'=267 +'TIMESTAMPADD'=268 +'TIMESTAMPDIFF'=269 +'TO'=270 +'TOUCH'=271 +'TRAILING'=272 +'TRANSACTION'=273 +'TRANSACTIONS'=274 +'TRANSFORM'=275 +'TRIM'=276 +'TRUE'=277 +'TRUNCATE'=278 +'TRY_CAST'=279 +'TYPE'=280 +'UNARCHIVE'=281 +'UNBOUNDED'=282 +'UNCACHE'=283 +'UNION'=284 +'UNIQUE'=285 +'UNKNOWN'=286 +'UNLOCK'=287 +'UNPIVOT'=288 +'UNSET'=289 +'UPDATE'=290 +'USE'=291 +'USER'=292 +'USING'=293 +'VALUES'=294 +'VERSION'=295 +'VIEW'=296 +'VIEWS'=297 +'WEEK'=298 +'WEEKS'=299 +'WHEN'=300 +'WHERE'=301 +'WINDOW'=302 +'WITH'=303 +'WITHIN'=304 +'YEAR'=305 +'YEARS'=306 +'ZONE'=307 +'<=>'=309 +'<>'=310 +'!='=311 +'<'=312 +'>'=314 +'+'=316 +'-'=317 +'*'=318 +'/'=319 +'%'=320 +'~'=321 +'&'=322 +'|'=323 +'||'=324 +'^'=325 +':'=326 +'->'=327 +'/*+'=328 +'*/'=329 diff --git a/datajunction-server/datajunction_server/sql/parsing/backends/grammar/generated/SqlBaseParser.interp b/datajunction-server/datajunction_server/sql/parsing/backends/grammar/generated/SqlBaseParser.interp new file mode 100644 index 000000000..f7c974533 --- /dev/null +++ b/datajunction-server/datajunction_server/sql/parsing/backends/grammar/generated/SqlBaseParser.interp @@ -0,0 +1,878 @@ +token literal names: +null +';' +'(' +')' +',' +'.' +'[' +']' +'ADD' +'AFTER' +'ALL' +'ALTER' +'ALWAYS' +'ANALYZE' +'AND' +'ANTI' +'ANY' +'ANY_VALUE' +'ARCHIVE' +'ARRAY' +'AS' +'ASC' +'AT' +'AUTHORIZATION' +'BETWEEN' +'BOTH' +'BUCKET' +'BUCKETS' +'BY' +'CACHE' +'CASCADE' +'CASE' +'CAST' +'CATALOG' +'CATALOGS' +'CHANGE' +'CHECK' +'CLEAR' +'CLUSTER' +'CLUSTERED' +'CODEGEN' +'COLLATE' +'COLLECTION' +'COLUMN' +'COLUMNS' +'COMMENT' +'COMMIT' +'COMPACT' +'COMPACTIONS' +'COMPUTE' +'CONCATENATE' +'CONSTRAINT' +'COST' +'CREATE' +'CROSS' +'CUBE' +'CURRENT' +'CURRENT_DATE' +'CURRENT_TIME' +'CURRENT_TIMESTAMP' +'CURRENT_USER' +'DAY' +'DAYS' +'DAYOFYEAR' +'DATA' +'DATABASE' +'DATABASES' +'DATEADD' +'DATEDIFF' +'DBPROPERTIES' +'DEFAULT' +'DEFINED' +'DELETE' +'DELIMITED' +'DESC' +'DESCRIBE' +'DFS' +'DIRECTORIES' +'DIRECTORY' +'DISTINCT' +'DISTRIBUTE' +'DIV' +'DROP' +'ELSE' +'END' +'ESCAPE' +'ESCAPED' +'EXCEPT' +'EXCHANGE' +'EXCLUDE' +'EXISTS' +'EXPLAIN' +'EXPORT' +'EXTENDED' +'EXTERNAL' +'EXTRACT' +'FALSE' +'FETCH' +'FIELDS' +'FILTER' +'FILEFORMAT' +'FIRST' +'FOLLOWING' +'FOR' +'FOREIGN' +'FORMAT' +'FORMATTED' +'FROM' +'FULL' +'FUNCTION' +'FUNCTIONS' +'GENERATED' +'GLOBAL' +'GRANT' +'GROUP' +'GROUPING' +'HAVING' +'HOUR' +'HOURS' +'IF' +'IGNORE' +'IMPORT' +'IN' +'INCLUDE' +'INDEX' +'INDEXES' +'INNER' +'INPATH' +'INPUTFORMAT' +'INSERT' +'INTERSECT' +'INTERVAL' +'INTO' +'IS' +'ITEMS' +'JOIN' +'KEYS' +'LAST' +'LATERAL' +'LAZY' +'LEADING' +'LEFT' +'LIKE' +'ILIKE' +'LIMIT' +'LINES' +'LIST' +'LOAD' +'LOCAL' +'LOCATION' +'LOCK' +'LOCKS' +'LOGICAL' +'MACRO' +'MAP' +'MATCHED' +'MERGE' +'MICROSECOND' +'MICROSECONDS' +'MILLISECOND' +'MILLISECONDS' +'MINUTE' +'MINUTES' +'MONTH' +'MONTHS' +'MSCK' +'NAMESPACE' +'NAMESPACES' +'NANOSECOND' +'NANOSECONDS' +'NATURAL' +'NO' +null +'NULL' +'NULLS' +'OF' +'OFFSET' +'ON' +'ONLY' +'OPTION' +'OPTIONS' +'OR' +'ORDER' +'OUT' +'OUTER' +'OUTPUTFORMAT' +'OVER' +'OVERLAPS' +'OVERLAY' +'OVERWRITE' +'PARTITION' +'PARTITIONED' +'PARTITIONS' +'PERCENTILE_CONT' +'PERCENTILE_DISC' +'PERCENT' +'PIVOT' +'PLACING' +'POSITION' +'PRECEDING' +'PRIMARY' +'PRINCIPALS' +'PROPERTIES' +'PURGE' +'QUARTER' +'QUERY' +'RANGE' +'RECORDREADER' +'RECORDWRITER' +'RECOVER' +'REDUCE' +'REFERENCES' +'REFRESH' +'RENAME' +'REPAIR' +'REPEATABLE' +'REPLACE' +'RESET' +'RESPECT' +'RESTRICT' +'REVOKE' +'RIGHT' +null +'ROLE' +'ROLES' +'ROLLBACK' +'ROLLUP' +'ROW' +'ROWS' +'SECOND' +'SECONDS' +'SCHEMA' +'SCHEMAS' +'SELECT' +'SEMI' +'SEPARATED' +'SERDE' +'SERDEPROPERTIES' +'SESSION_USER' +'SET' +'MINUS' +'SETS' +'SHOW' +'SKEWED' +'SOME' +'SORT' +'SORTED' +'SOURCE' +'START' +'STATISTICS' +'STORED' +'STRATIFY' +'STRUCT' +'SUBSTR' +'SUBSTRING' +'SYNC' +'SYSTEM_TIME' +'SYSTEM_VERSION' +'TABLE' +'TABLES' +'TABLESAMPLE' +'TARGET' +'TBLPROPERTIES' +null +'TERMINATED' +'THEN' +'TIME' +'TIMESTAMP' +'TIMESTAMPADD' +'TIMESTAMPDIFF' +'TO' +'TOUCH' +'TRAILING' +'TRANSACTION' +'TRANSACTIONS' +'TRANSFORM' +'TRIM' +'TRUE' +'TRUNCATE' +'TRY_CAST' +'TYPE' +'UNARCHIVE' +'UNBOUNDED' +'UNCACHE' +'UNION' +'UNIQUE' +'UNKNOWN' +'UNLOCK' +'UNPIVOT' +'UNSET' +'UPDATE' +'USE' +'USER' +'USING' +'VALUES' +'VERSION' +'VIEW' +'VIEWS' +'WEEK' +'WEEKS' +'WHEN' +'WHERE' +'WINDOW' +'WITH' +'WITHIN' +'YEAR' +'YEARS' +'ZONE' +null +'<=>' +'<>' +'!=' +'<' +null +'>' +null +'+' +'-' +'*' +'/' +'%' +'~' +'&' +'|' +'||' +'^' +':' +'->' +'/*+' +'*/' +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null + +token symbolic names: +null +SEMICOLON +LEFT_PAREN +RIGHT_PAREN +COMMA +DOT +LEFT_BRACKET +RIGHT_BRACKET +ADD +AFTER +ALL +ALTER +ALWAYS +ANALYZE +AND +ANTI +ANY +ANY_VALUE +ARCHIVE +ARRAY +AS +ASC +AT +AUTHORIZATION +BETWEEN +BOTH +BUCKET +BUCKETS +BY +CACHE +CASCADE +CASE +CAST +CATALOG +CATALOGS +CHANGE +CHECK +CLEAR +CLUSTER +CLUSTERED +CODEGEN +COLLATE +COLLECTION +COLUMN +COLUMNS +COMMENT +COMMIT +COMPACT +COMPACTIONS +COMPUTE +CONCATENATE +CONSTRAINT +COST +CREATE +CROSS +CUBE +CURRENT +CURRENT_DATE +CURRENT_TIME +CURRENT_TIMESTAMP +CURRENT_USER +DAY +DAYS +DAYOFYEAR +DATA +DATABASE +DATABASES +DATEADD +DATEDIFF +DBPROPERTIES +DEFAULT +DEFINED +DELETE +DELIMITED +DESC +DESCRIBE +DFS +DIRECTORIES +DIRECTORY +DISTINCT +DISTRIBUTE +DIV +DROP +ELSE +END +ESCAPE +ESCAPED +EXCEPT +EXCHANGE +EXCLUDE +EXISTS +EXPLAIN +EXPORT +EXTENDED +EXTERNAL +EXTRACT +FALSE +FETCH +FIELDS +FILTER +FILEFORMAT +FIRST +FOLLOWING +FOR +FOREIGN +FORMAT +FORMATTED +FROM +FULL +FUNCTION +FUNCTIONS +GENERATED +GLOBAL +GRANT +GROUP +GROUPING +HAVING +HOUR +HOURS +IF +IGNORE +IMPORT +IN +INCLUDE +INDEX +INDEXES +INNER +INPATH +INPUTFORMAT +INSERT +INTERSECT +INTERVAL +INTO +IS +ITEMS +JOIN +KEYS +LAST +LATERAL +LAZY +LEADING +LEFT +LIKE +ILIKE +LIMIT +LINES +LIST +LOAD +LOCAL +LOCATION +LOCK +LOCKS +LOGICAL +MACRO +MAP +MATCHED +MERGE +MICROSECOND +MICROSECONDS +MILLISECOND +MILLISECONDS +MINUTE +MINUTES +MONTH +MONTHS +MSCK +NAMESPACE +NAMESPACES +NANOSECOND +NANOSECONDS +NATURAL +NO +NOT +NULL +NULLS +OF +OFFSET +ON +ONLY +OPTION +OPTIONS +OR +ORDER +OUT +OUTER +OUTPUTFORMAT +OVER +OVERLAPS +OVERLAY +OVERWRITE +PARTITION +PARTITIONED +PARTITIONS +PERCENTILE_CONT +PERCENTILE_DISC +PERCENTLIT +PIVOT +PLACING +POSITION +PRECEDING +PRIMARY +PRINCIPALS +PROPERTIES +PURGE +QUARTER +QUERY +RANGE +RECORDREADER +RECORDWRITER +RECOVER +REDUCE +REFERENCES +REFRESH +RENAME +REPAIR +REPEATABLE +REPLACE +RESET +RESPECT +RESTRICT +REVOKE +RIGHT +RLIKE +ROLE +ROLES +ROLLBACK +ROLLUP +ROW +ROWS +SECOND +SECONDS +SCHEMA +SCHEMAS +SELECT +SEMI +SEPARATED +SERDE +SERDEPROPERTIES +SESSION_USER +SET +SETMINUS +SETS +SHOW +SKEWED +SOME +SORT +SORTED +SOURCE +START +STATISTICS +STORED +STRATIFY +STRUCT +SUBSTR +SUBSTRING +SYNC +SYSTEM_TIME +SYSTEM_VERSION +TABLE +TABLES +TABLESAMPLE +TARGET +TBLPROPERTIES +TEMPORARY +TERMINATED +THEN +TIME +TIMESTAMP +TIMESTAMPADD +TIMESTAMPDIFF +TO +TOUCH +TRAILING +TRANSACTION +TRANSACTIONS +TRANSFORM +TRIM +TRUE +TRUNCATE +TRY_CAST +TYPE +UNARCHIVE +UNBOUNDED +UNCACHE +UNION +UNIQUE +UNKNOWN +UNLOCK +UNPIVOT +UNSET +UPDATE +USE +USER +USING +VALUES +VERSION +VIEW +VIEWS +WEEK +WEEKS +WHEN +WHERE +WINDOW +WITH +WITHIN +YEAR +YEARS +ZONE +EQ +NSEQ +NEQ +NEQJ +LT +LTE +GT +GTE +PLUS +MINUS +ASTERISK +SLASH +PERCENT +TILDE +AMPERSAND +PIPE +CONCAT_PIPE +HAT +COLON +ARROW +HENT_START +HENT_END +STRING +DOUBLEQUOTED_STRING +BIGINT_LITERAL +SMALLINT_LITERAL +TINYINT_LITERAL +INTEGER_VALUE +EXPONENT_VALUE +DECIMAL_VALUE +FLOAT_LITERAL +DOUBLE_LITERAL +BIGDECIMAL_LITERAL +IDENTIFIER +BACKQUOTED_IDENTIFIER +SIMPLE_COMMENT +BRACKETED_COMMENT +WS +UNRECOGNIZED + +rule names: +singleStatement +singleExpression +singleTableIdentifier +singleMultipartIdentifier +singleFunctionIdentifier +singleDataType +singleTableSchema +statement +timezone +configKey +configValue +unsupportedHiveNativeCommands +createTableHeader +replaceTableHeader +bucketSpec +skewSpec +locationSpec +commentSpec +query +insertInto +partitionSpecLocation +partitionSpec +partitionVal +namespace +namespaces +describeFuncName +describeColName +ctes +namedQuery +tableProvider +createTableClauses +propertyList +property +propertyKey +propertyValue +constantList +nestedConstantList +createFileFormat +fileFormat +storageHandler +resource +dmlStatementNoWith +queryOrganization +multiInsertQueryBody +queryTerm +queryPrimary +sortItem +fromStatement +fromStatementBody +querySpecification +transformClause +selectClause +setClause +matchedClause +notMatchedClause +notMatchedBySourceClause +matchedAction +notMatchedAction +notMatchedBySourceAction +assignmentList +assignment +whereClause +havingClause +hint +hintStatement +fromClause +temporalClause +aggregationClause +groupByClause +groupingAnalytics +groupingElement +groupingSet +pivotClause +pivotColumn +pivotValue +unpivotClause +unpivotNullClause +unpivotOperator +unpivotSingleValueColumnClause +unpivotMultiValueColumnClause +unpivotColumnSet +unpivotValueColumn +unpivotNameColumn +unpivotColumnAndAlias +unpivotColumn +unpivotAlias +lateralView +setQuantifier +relation +relationExtension +joinRelation +joinType +joinCriteria +sample +sampleMethod +identifierList +identifierSeq +orderedIdentifierList +orderedIdentifier +identifierCommentList +identifierComment +relationPrimary +inlineTable +functionTable +tableAlias +rowFormat +multipartIdentifierList +multipartIdentifier +multipartIdentifierPropertyList +multipartIdentifierProperty +tableIdentifier +functionIdentifier +namedExpression +namedExpressionSeq +partitionFieldList +partitionField +transform +transformArgument +expression +expressionSeq +booleanExpression +predicate +valueExpression +datetimeUnit +primaryExpression +constant +comparisonOperator +arithmeticOperator +predicateOperator +booleanValue +interval +errorCapturingMultiUnitsInterval +multiUnitsInterval +errorCapturingUnitToUnitInterval +unitToUnitInterval +intervalValue +unitInMultiUnits +unitInUnitToUnit +colPosition +dataType +qualifiedColTypeWithPositionList +qualifiedColTypeWithPosition +colDefinitionDescriptorWithPosition +defaultExpression +colTypeList +colType +createOrReplaceTableColTypeList +createOrReplaceTableColType +colDefinitionOption +generationExpression +complexColTypeList +complexColType +whenClause +windowClause +namedWindow +windowSpec +windowFrame +frameBound +qualifiedNameList +functionName +qualifiedName +errorCapturingIdentifier +errorCapturingIdentifierExtra +identifier +strictIdentifier +quotedIdentifier +backQuotedIdentifier +number +alterColumnAction +stringLit +comment +version +ansiNonReserved +strictNonReserved +nonReserved + + +atn: +[4, 1, 346, 3549, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 2, 62, 7, 62, 2, 63, 7, 63, 2, 64, 7, 64, 2, 65, 7, 65, 2, 66, 7, 66, 2, 67, 7, 67, 2, 68, 7, 68, 2, 69, 7, 69, 2, 70, 7, 70, 2, 71, 7, 71, 2, 72, 7, 72, 2, 73, 7, 73, 2, 74, 7, 74, 2, 75, 7, 75, 2, 76, 7, 76, 2, 77, 7, 77, 2, 78, 7, 78, 2, 79, 7, 79, 2, 80, 7, 80, 2, 81, 7, 81, 2, 82, 7, 82, 2, 83, 7, 83, 2, 84, 7, 84, 2, 85, 7, 85, 2, 86, 7, 86, 2, 87, 7, 87, 2, 88, 7, 88, 2, 89, 7, 89, 2, 90, 7, 90, 2, 91, 7, 91, 2, 92, 7, 92, 2, 93, 7, 93, 2, 94, 7, 94, 2, 95, 7, 95, 2, 96, 7, 96, 2, 97, 7, 97, 2, 98, 7, 98, 2, 99, 7, 99, 2, 100, 7, 100, 2, 101, 7, 101, 2, 102, 7, 102, 2, 103, 7, 103, 2, 104, 7, 104, 2, 105, 7, 105, 2, 106, 7, 106, 2, 107, 7, 107, 2, 108, 7, 108, 2, 109, 7, 109, 2, 110, 7, 110, 2, 111, 7, 111, 2, 112, 7, 112, 2, 113, 7, 113, 2, 114, 7, 114, 2, 115, 7, 115, 2, 116, 7, 116, 2, 117, 7, 117, 2, 118, 7, 118, 2, 119, 7, 119, 2, 120, 7, 120, 2, 121, 7, 121, 2, 122, 7, 122, 2, 123, 7, 123, 2, 124, 7, 124, 2, 125, 7, 125, 2, 126, 7, 126, 2, 127, 7, 127, 2, 128, 7, 128, 2, 129, 7, 129, 2, 130, 7, 130, 2, 131, 7, 131, 2, 132, 7, 132, 2, 133, 7, 133, 2, 134, 7, 134, 2, 135, 7, 135, 2, 136, 7, 136, 2, 137, 7, 137, 2, 138, 7, 138, 2, 139, 7, 139, 2, 140, 7, 140, 2, 141, 7, 141, 2, 142, 7, 142, 2, 143, 7, 143, 2, 144, 7, 144, 2, 145, 7, 145, 2, 146, 7, 146, 2, 147, 7, 147, 2, 148, 7, 148, 2, 149, 7, 149, 2, 150, 7, 150, 2, 151, 7, 151, 2, 152, 7, 152, 2, 153, 7, 153, 2, 154, 7, 154, 2, 155, 7, 155, 2, 156, 7, 156, 2, 157, 7, 157, 2, 158, 7, 158, 2, 159, 7, 159, 2, 160, 7, 160, 2, 161, 7, 161, 2, 162, 7, 162, 2, 163, 7, 163, 2, 164, 7, 164, 2, 165, 7, 165, 2, 166, 7, 166, 2, 167, 7, 167, 2, 168, 7, 168, 2, 169, 7, 169, 2, 170, 7, 170, 2, 171, 7, 171, 2, 172, 7, 172, 2, 173, 7, 173, 2, 174, 7, 174, 1, 0, 1, 0, 5, 0, 353, 8, 0, 10, 0, 12, 0, 356, 9, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 3, 7, 380, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 393, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 400, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 5, 7, 408, 8, 7, 10, 7, 12, 7, 411, 9, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 430, 8, 7, 1, 7, 1, 7, 3, 7, 434, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 440, 8, 7, 1, 7, 3, 7, 443, 8, 7, 1, 7, 3, 7, 446, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 453, 8, 7, 1, 7, 3, 7, 456, 8, 7, 1, 7, 1, 7, 3, 7, 460, 8, 7, 1, 7, 3, 7, 463, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 470, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 5, 7, 481, 8, 7, 10, 7, 12, 7, 484, 9, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 491, 8, 7, 1, 7, 3, 7, 494, 8, 7, 1, 7, 1, 7, 3, 7, 498, 8, 7, 1, 7, 3, 7, 501, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 507, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 518, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 524, 8, 7, 1, 7, 1, 7, 1, 7, 3, 7, 529, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 563, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 576, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 601, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 610, 8, 7, 1, 7, 1, 7, 3, 7, 614, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 620, 8, 7, 1, 7, 1, 7, 3, 7, 624, 8, 7, 1, 7, 1, 7, 1, 7, 3, 7, 629, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 635, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 647, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 655, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 661, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 674, 8, 7, 1, 7, 4, 7, 677, 8, 7, 11, 7, 12, 7, 678, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 695, 8, 7, 1, 7, 1, 7, 1, 7, 5, 7, 700, 8, 7, 10, 7, 12, 7, 703, 9, 7, 1, 7, 3, 7, 706, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 712, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 727, 8, 7, 1, 7, 1, 7, 3, 7, 731, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 737, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 743, 8, 7, 1, 7, 3, 7, 746, 8, 7, 1, 7, 3, 7, 749, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 755, 8, 7, 1, 7, 1, 7, 3, 7, 759, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 5, 7, 767, 8, 7, 10, 7, 12, 7, 770, 9, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 778, 8, 7, 1, 7, 3, 7, 781, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 790, 8, 7, 1, 7, 1, 7, 1, 7, 3, 7, 795, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 801, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 808, 8, 7, 1, 7, 3, 7, 811, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 817, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 5, 7, 826, 8, 7, 10, 7, 12, 7, 829, 9, 7, 3, 7, 831, 8, 7, 1, 7, 1, 7, 3, 7, 835, 8, 7, 1, 7, 1, 7, 1, 7, 3, 7, 840, 8, 7, 1, 7, 1, 7, 1, 7, 3, 7, 845, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 852, 8, 7, 1, 7, 3, 7, 855, 8, 7, 1, 7, 3, 7, 858, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 865, 8, 7, 1, 7, 1, 7, 1, 7, 3, 7, 870, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 879, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 887, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 893, 8, 7, 1, 7, 3, 7, 896, 8, 7, 1, 7, 3, 7, 899, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 905, 8, 7, 1, 7, 1, 7, 3, 7, 909, 8, 7, 1, 7, 1, 7, 1, 7, 3, 7, 914, 8, 7, 1, 7, 3, 7, 917, 8, 7, 1, 7, 1, 7, 3, 7, 921, 8, 7, 3, 7, 923, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 931, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 939, 8, 7, 1, 7, 3, 7, 942, 8, 7, 1, 7, 1, 7, 1, 7, 3, 7, 947, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 953, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 959, 8, 7, 1, 7, 3, 7, 962, 8, 7, 1, 7, 1, 7, 3, 7, 966, 8, 7, 1, 7, 3, 7, 969, 8, 7, 1, 7, 1, 7, 3, 7, 973, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 5, 7, 999, 8, 7, 10, 7, 12, 7, 1002, 9, 7, 3, 7, 1004, 8, 7, 1, 7, 1, 7, 3, 7, 1008, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 1014, 8, 7, 1, 7, 3, 7, 1017, 8, 7, 1, 7, 3, 7, 1020, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 1026, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 1034, 8, 7, 1, 7, 1, 7, 1, 7, 3, 7, 1039, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 1045, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 1051, 8, 7, 1, 7, 3, 7, 1054, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 1061, 8, 7, 1, 7, 1, 7, 1, 7, 5, 7, 1066, 8, 7, 10, 7, 12, 7, 1069, 9, 7, 1, 7, 1, 7, 1, 7, 5, 7, 1074, 8, 7, 10, 7, 12, 7, 1077, 9, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 5, 7, 1091, 8, 7, 10, 7, 12, 7, 1094, 9, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 5, 7, 1105, 8, 7, 10, 7, 12, 7, 1108, 9, 7, 3, 7, 1110, 8, 7, 1, 7, 1, 7, 5, 7, 1114, 8, 7, 10, 7, 12, 7, 1117, 9, 7, 1, 7, 1, 7, 1, 7, 1, 7, 5, 7, 1123, 8, 7, 10, 7, 12, 7, 1126, 9, 7, 1, 7, 1, 7, 1, 7, 1, 7, 5, 7, 1132, 8, 7, 10, 7, 12, 7, 1135, 9, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 1142, 8, 7, 1, 7, 1, 7, 1, 7, 3, 7, 1147, 8, 7, 1, 7, 1, 7, 1, 7, 3, 7, 1152, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 1159, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 1165, 8, 7, 1, 7, 1, 7, 1, 7, 3, 7, 1170, 8, 7, 1, 7, 1, 7, 1, 7, 1, 7, 5, 7, 1176, 8, 7, 10, 7, 12, 7, 1179, 9, 7, 3, 7, 1181, 8, 7, 1, 8, 1, 8, 3, 8, 1185, 8, 8, 1, 9, 1, 9, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 3, 11, 1197, 8, 11, 1, 11, 1, 11, 3, 11, 1201, 8, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 3, 11, 1208, 8, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 3, 11, 1324, 8, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 3, 11, 1332, 8, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 3, 11, 1340, 8, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 3, 11, 1349, 8, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 3, 11, 1359, 8, 11, 1, 12, 1, 12, 3, 12, 1363, 8, 12, 1, 12, 3, 12, 1366, 8, 12, 1, 12, 1, 12, 1, 12, 1, 12, 3, 12, 1372, 8, 12, 1, 12, 1, 12, 1, 13, 1, 13, 3, 13, 1378, 8, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 3, 14, 1390, 8, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 3, 15, 1402, 8, 15, 1, 15, 1, 15, 1, 15, 3, 15, 1407, 8, 15, 1, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 18, 3, 18, 1416, 8, 18, 1, 18, 1, 18, 1, 18, 1, 19, 1, 19, 1, 19, 3, 19, 1424, 8, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 3, 19, 1431, 8, 19, 3, 19, 1433, 8, 19, 1, 19, 3, 19, 1436, 8, 19, 1, 19, 1, 19, 1, 19, 3, 19, 1441, 8, 19, 1, 19, 1, 19, 3, 19, 1445, 8, 19, 1, 19, 1, 19, 1, 19, 3, 19, 1450, 8, 19, 1, 19, 3, 19, 1453, 8, 19, 1, 19, 1, 19, 1, 19, 3, 19, 1458, 8, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 3, 19, 1467, 8, 19, 1, 19, 1, 19, 1, 19, 3, 19, 1472, 8, 19, 1, 19, 3, 19, 1475, 8, 19, 1, 19, 1, 19, 1, 19, 3, 19, 1480, 8, 19, 1, 19, 1, 19, 3, 19, 1484, 8, 19, 1, 19, 1, 19, 1, 19, 3, 19, 1489, 8, 19, 3, 19, 1491, 8, 19, 1, 20, 1, 20, 3, 20, 1495, 8, 20, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 5, 21, 1502, 8, 21, 10, 21, 12, 21, 1505, 9, 21, 1, 21, 1, 21, 1, 22, 1, 22, 1, 22, 3, 22, 1512, 8, 22, 1, 22, 1, 22, 1, 22, 1, 22, 3, 22, 1518, 8, 22, 1, 23, 1, 23, 1, 24, 1, 24, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 3, 25, 1529, 8, 25, 1, 26, 1, 26, 1, 26, 5, 26, 1534, 8, 26, 10, 26, 12, 26, 1537, 9, 26, 1, 27, 1, 27, 1, 27, 1, 27, 5, 27, 1543, 8, 27, 10, 27, 12, 27, 1546, 9, 27, 1, 28, 1, 28, 3, 28, 1550, 8, 28, 1, 28, 3, 28, 1553, 8, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 29, 1, 29, 1, 29, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 5, 30, 1575, 8, 30, 10, 30, 12, 30, 1578, 9, 30, 1, 31, 1, 31, 1, 31, 1, 31, 5, 31, 1584, 8, 31, 10, 31, 12, 31, 1587, 9, 31, 1, 31, 1, 31, 1, 32, 1, 32, 3, 32, 1593, 8, 32, 1, 32, 3, 32, 1596, 8, 32, 1, 33, 1, 33, 1, 33, 5, 33, 1601, 8, 33, 10, 33, 12, 33, 1604, 9, 33, 1, 33, 3, 33, 1607, 8, 33, 1, 34, 1, 34, 1, 34, 1, 34, 3, 34, 1613, 8, 34, 1, 35, 1, 35, 1, 35, 1, 35, 5, 35, 1619, 8, 35, 10, 35, 12, 35, 1622, 9, 35, 1, 35, 1, 35, 1, 36, 1, 36, 1, 36, 1, 36, 5, 36, 1630, 8, 36, 10, 36, 12, 36, 1633, 9, 36, 1, 36, 1, 36, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 3, 37, 1643, 8, 37, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 3, 38, 1651, 8, 38, 1, 39, 1, 39, 1, 39, 1, 39, 3, 39, 1657, 8, 39, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 4, 41, 1667, 8, 41, 11, 41, 12, 41, 1668, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 3, 41, 1676, 8, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 3, 41, 1683, 8, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 3, 41, 1695, 8, 41, 1, 41, 1, 41, 1, 41, 1, 41, 5, 41, 1701, 8, 41, 10, 41, 12, 41, 1704, 9, 41, 1, 41, 5, 41, 1707, 8, 41, 10, 41, 12, 41, 1710, 9, 41, 1, 41, 5, 41, 1713, 8, 41, 10, 41, 12, 41, 1716, 9, 41, 3, 41, 1718, 8, 41, 1, 42, 1, 42, 1, 42, 1, 42, 1, 42, 5, 42, 1725, 8, 42, 10, 42, 12, 42, 1728, 9, 42, 3, 42, 1730, 8, 42, 1, 42, 1, 42, 1, 42, 1, 42, 1, 42, 5, 42, 1737, 8, 42, 10, 42, 12, 42, 1740, 9, 42, 3, 42, 1742, 8, 42, 1, 42, 1, 42, 1, 42, 1, 42, 1, 42, 5, 42, 1749, 8, 42, 10, 42, 12, 42, 1752, 9, 42, 3, 42, 1754, 8, 42, 1, 42, 1, 42, 1, 42, 1, 42, 1, 42, 5, 42, 1761, 8, 42, 10, 42, 12, 42, 1764, 9, 42, 3, 42, 1766, 8, 42, 1, 42, 3, 42, 1769, 8, 42, 1, 42, 1, 42, 1, 42, 3, 42, 1774, 8, 42, 3, 42, 1776, 8, 42, 1, 42, 1, 42, 3, 42, 1780, 8, 42, 1, 43, 1, 43, 1, 43, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 3, 44, 1791, 8, 44, 1, 44, 5, 44, 1794, 8, 44, 10, 44, 12, 44, 1797, 9, 44, 1, 45, 1, 45, 1, 45, 1, 45, 1, 45, 1, 45, 1, 45, 1, 45, 1, 45, 3, 45, 1808, 8, 45, 1, 46, 1, 46, 3, 46, 1812, 8, 46, 1, 46, 1, 46, 3, 46, 1816, 8, 46, 1, 47, 1, 47, 4, 47, 1820, 8, 47, 11, 47, 12, 47, 1821, 1, 48, 1, 48, 3, 48, 1826, 8, 48, 1, 48, 1, 48, 1, 48, 1, 48, 5, 48, 1832, 8, 48, 10, 48, 12, 48, 1835, 9, 48, 1, 48, 3, 48, 1838, 8, 48, 1, 48, 3, 48, 1841, 8, 48, 1, 48, 3, 48, 1844, 8, 48, 1, 48, 3, 48, 1847, 8, 48, 1, 48, 1, 48, 3, 48, 1851, 8, 48, 1, 49, 1, 49, 3, 49, 1855, 8, 49, 1, 49, 5, 49, 1858, 8, 49, 10, 49, 12, 49, 1861, 9, 49, 1, 49, 3, 49, 1864, 8, 49, 1, 49, 3, 49, 1867, 8, 49, 1, 49, 3, 49, 1870, 8, 49, 1, 49, 3, 49, 1873, 8, 49, 1, 49, 1, 49, 3, 49, 1877, 8, 49, 1, 49, 5, 49, 1880, 8, 49, 10, 49, 12, 49, 1883, 9, 49, 1, 49, 3, 49, 1886, 8, 49, 1, 49, 3, 49, 1889, 8, 49, 1, 49, 3, 49, 1892, 8, 49, 1, 49, 3, 49, 1895, 8, 49, 3, 49, 1897, 8, 49, 1, 50, 1, 50, 1, 50, 1, 50, 3, 50, 1903, 8, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 3, 50, 1910, 8, 50, 1, 50, 1, 50, 1, 50, 3, 50, 1915, 8, 50, 1, 50, 3, 50, 1918, 8, 50, 1, 50, 3, 50, 1921, 8, 50, 1, 50, 1, 50, 3, 50, 1925, 8, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 3, 50, 1935, 8, 50, 1, 50, 1, 50, 3, 50, 1939, 8, 50, 3, 50, 1941, 8, 50, 1, 50, 3, 50, 1944, 8, 50, 1, 50, 1, 50, 3, 50, 1948, 8, 50, 1, 51, 1, 51, 5, 51, 1952, 8, 51, 10, 51, 12, 51, 1955, 9, 51, 1, 51, 3, 51, 1958, 8, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 1969, 8, 53, 1, 53, 1, 53, 1, 53, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 3, 54, 1979, 8, 54, 1, 54, 1, 54, 3, 54, 1983, 8, 54, 1, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 3, 55, 1995, 8, 55, 1, 55, 1, 55, 1, 55, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 3, 56, 2007, 8, 56, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 5, 57, 2020, 8, 57, 10, 57, 12, 57, 2023, 9, 57, 1, 57, 1, 57, 3, 57, 2027, 8, 57, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 2033, 8, 58, 1, 59, 1, 59, 1, 59, 5, 59, 2038, 8, 59, 10, 59, 12, 59, 2041, 9, 59, 1, 60, 1, 60, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, 1, 62, 1, 62, 1, 62, 1, 63, 1, 63, 1, 63, 3, 63, 2056, 8, 63, 1, 63, 5, 63, 2059, 8, 63, 10, 63, 12, 63, 2062, 9, 63, 1, 63, 1, 63, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 5, 64, 2072, 8, 64, 10, 64, 12, 64, 2075, 9, 64, 1, 64, 1, 64, 3, 64, 2079, 8, 64, 1, 65, 1, 65, 1, 65, 1, 65, 5, 65, 2085, 8, 65, 10, 65, 12, 65, 2088, 9, 65, 1, 65, 5, 65, 2091, 8, 65, 10, 65, 12, 65, 2094, 9, 65, 1, 65, 3, 65, 2097, 8, 65, 1, 65, 3, 65, 2100, 8, 65, 1, 66, 3, 66, 2103, 8, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 3, 66, 2110, 8, 66, 1, 66, 1, 66, 1, 66, 1, 66, 3, 66, 2116, 8, 66, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 5, 67, 2123, 8, 67, 10, 67, 12, 67, 2126, 9, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 5, 67, 2133, 8, 67, 10, 67, 12, 67, 2136, 9, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 5, 67, 2148, 8, 67, 10, 67, 12, 67, 2151, 9, 67, 1, 67, 1, 67, 3, 67, 2155, 8, 67, 3, 67, 2157, 8, 67, 1, 68, 1, 68, 3, 68, 2161, 8, 68, 1, 69, 1, 69, 1, 69, 1, 69, 1, 69, 5, 69, 2168, 8, 69, 10, 69, 12, 69, 2171, 9, 69, 1, 69, 1, 69, 1, 69, 1, 69, 1, 69, 1, 69, 1, 69, 1, 69, 5, 69, 2181, 8, 69, 10, 69, 12, 69, 2184, 9, 69, 1, 69, 1, 69, 3, 69, 2188, 8, 69, 1, 70, 1, 70, 3, 70, 2192, 8, 70, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 2198, 8, 71, 10, 71, 12, 71, 2201, 9, 71, 3, 71, 2203, 8, 71, 1, 71, 1, 71, 3, 71, 2207, 8, 71, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 5, 72, 2219, 8, 72, 10, 72, 12, 72, 2222, 9, 72, 1, 72, 1, 72, 1, 72, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 5, 73, 2232, 8, 73, 10, 73, 12, 73, 2235, 9, 73, 1, 73, 1, 73, 3, 73, 2239, 8, 73, 1, 74, 1, 74, 3, 74, 2243, 8, 74, 1, 74, 3, 74, 2246, 8, 74, 1, 75, 1, 75, 3, 75, 2250, 8, 75, 1, 75, 1, 75, 1, 75, 1, 75, 3, 75, 2256, 8, 75, 1, 75, 3, 75, 2259, 8, 75, 1, 76, 1, 76, 1, 76, 1, 77, 1, 77, 3, 77, 2266, 8, 77, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 5, 78, 2276, 8, 78, 10, 78, 12, 78, 2279, 9, 78, 1, 78, 1, 78, 1, 79, 1, 79, 1, 79, 1, 79, 5, 79, 2287, 8, 79, 10, 79, 12, 79, 2290, 9, 79, 1, 79, 1, 79, 1, 79, 1, 79, 1, 79, 1, 79, 1, 79, 1, 79, 5, 79, 2300, 8, 79, 10, 79, 12, 79, 2303, 9, 79, 1, 79, 1, 79, 1, 80, 1, 80, 1, 80, 1, 80, 5, 80, 2311, 8, 80, 10, 80, 12, 80, 2314, 9, 80, 1, 80, 1, 80, 3, 80, 2318, 8, 80, 1, 81, 1, 81, 1, 82, 1, 82, 1, 83, 1, 83, 3, 83, 2326, 8, 83, 1, 84, 1, 84, 1, 85, 3, 85, 2331, 8, 85, 1, 85, 1, 85, 1, 86, 1, 86, 1, 86, 3, 86, 2338, 8, 86, 1, 86, 1, 86, 1, 86, 1, 86, 1, 86, 5, 86, 2345, 8, 86, 10, 86, 12, 86, 2348, 9, 86, 3, 86, 2350, 8, 86, 1, 86, 1, 86, 1, 86, 3, 86, 2355, 8, 86, 1, 86, 1, 86, 1, 86, 5, 86, 2360, 8, 86, 10, 86, 12, 86, 2363, 9, 86, 3, 86, 2365, 8, 86, 1, 87, 1, 87, 1, 88, 3, 88, 2370, 8, 88, 1, 88, 1, 88, 5, 88, 2374, 8, 88, 10, 88, 12, 88, 2377, 9, 88, 1, 89, 1, 89, 1, 89, 3, 89, 2382, 8, 89, 1, 90, 1, 90, 1, 90, 3, 90, 2387, 8, 90, 1, 90, 1, 90, 3, 90, 2391, 8, 90, 1, 90, 1, 90, 1, 90, 1, 90, 3, 90, 2397, 8, 90, 1, 90, 1, 90, 3, 90, 2401, 8, 90, 1, 91, 3, 91, 2404, 8, 91, 1, 91, 1, 91, 1, 91, 3, 91, 2409, 8, 91, 1, 91, 3, 91, 2412, 8, 91, 1, 91, 1, 91, 1, 91, 3, 91, 2417, 8, 91, 1, 91, 1, 91, 3, 91, 2421, 8, 91, 1, 91, 3, 91, 2424, 8, 91, 1, 91, 3, 91, 2427, 8, 91, 1, 92, 1, 92, 1, 92, 1, 92, 3, 92, 2433, 8, 92, 1, 93, 1, 93, 1, 93, 3, 93, 2438, 8, 93, 1, 93, 1, 93, 1, 93, 1, 93, 1, 93, 3, 93, 2445, 8, 93, 1, 94, 3, 94, 2448, 8, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 3, 94, 2466, 8, 94, 3, 94, 2468, 8, 94, 1, 94, 3, 94, 2471, 8, 94, 1, 95, 1, 95, 1, 95, 1, 95, 1, 96, 1, 96, 1, 96, 5, 96, 2480, 8, 96, 10, 96, 12, 96, 2483, 9, 96, 1, 97, 1, 97, 1, 97, 1, 97, 5, 97, 2489, 8, 97, 10, 97, 12, 97, 2492, 9, 97, 1, 97, 1, 97, 1, 98, 1, 98, 3, 98, 2498, 8, 98, 1, 99, 1, 99, 1, 99, 1, 99, 5, 99, 2504, 8, 99, 10, 99, 12, 99, 2507, 9, 99, 1, 99, 1, 99, 1, 100, 1, 100, 3, 100, 2513, 8, 100, 1, 101, 1, 101, 3, 101, 2517, 8, 101, 1, 101, 3, 101, 2520, 8, 101, 1, 101, 1, 101, 1, 101, 1, 101, 1, 101, 1, 101, 3, 101, 2528, 8, 101, 1, 101, 1, 101, 1, 101, 1, 101, 1, 101, 1, 101, 3, 101, 2536, 8, 101, 1, 101, 1, 101, 1, 101, 1, 101, 3, 101, 2542, 8, 101, 1, 102, 1, 102, 1, 102, 1, 102, 5, 102, 2548, 8, 102, 10, 102, 12, 102, 2551, 9, 102, 1, 102, 1, 102, 1, 103, 1, 103, 1, 103, 1, 103, 1, 103, 5, 103, 2560, 8, 103, 10, 103, 12, 103, 2563, 9, 103, 3, 103, 2565, 8, 103, 1, 103, 1, 103, 1, 103, 1, 104, 3, 104, 2571, 8, 104, 1, 104, 1, 104, 3, 104, 2575, 8, 104, 3, 104, 2577, 8, 104, 1, 105, 1, 105, 1, 105, 1, 105, 1, 105, 1, 105, 1, 105, 3, 105, 2586, 8, 105, 1, 105, 1, 105, 1, 105, 1, 105, 1, 105, 1, 105, 1, 105, 1, 105, 1, 105, 1, 105, 3, 105, 2598, 8, 105, 3, 105, 2600, 8, 105, 1, 105, 1, 105, 1, 105, 1, 105, 1, 105, 3, 105, 2607, 8, 105, 1, 105, 1, 105, 1, 105, 1, 105, 1, 105, 3, 105, 2614, 8, 105, 1, 105, 1, 105, 1, 105, 1, 105, 3, 105, 2620, 8, 105, 1, 105, 1, 105, 1, 105, 1, 105, 3, 105, 2626, 8, 105, 3, 105, 2628, 8, 105, 1, 106, 1, 106, 1, 106, 5, 106, 2633, 8, 106, 10, 106, 12, 106, 2636, 9, 106, 1, 107, 1, 107, 1, 107, 5, 107, 2641, 8, 107, 10, 107, 12, 107, 2644, 9, 107, 1, 108, 1, 108, 1, 108, 5, 108, 2649, 8, 108, 10, 108, 12, 108, 2652, 9, 108, 1, 109, 1, 109, 1, 109, 3, 109, 2657, 8, 109, 1, 110, 1, 110, 1, 110, 3, 110, 2662, 8, 110, 1, 110, 1, 110, 1, 111, 1, 111, 1, 111, 3, 111, 2669, 8, 111, 1, 111, 1, 111, 1, 112, 1, 112, 3, 112, 2675, 8, 112, 1, 112, 1, 112, 3, 112, 2679, 8, 112, 3, 112, 2681, 8, 112, 1, 113, 1, 113, 1, 113, 5, 113, 2686, 8, 113, 10, 113, 12, 113, 2689, 9, 113, 1, 114, 1, 114, 1, 114, 1, 114, 5, 114, 2695, 8, 114, 10, 114, 12, 114, 2698, 9, 114, 1, 114, 1, 114, 1, 115, 1, 115, 3, 115, 2704, 8, 115, 1, 116, 1, 116, 1, 116, 1, 116, 1, 116, 1, 116, 5, 116, 2712, 8, 116, 10, 116, 12, 116, 2715, 9, 116, 1, 116, 1, 116, 3, 116, 2719, 8, 116, 1, 117, 1, 117, 3, 117, 2723, 8, 117, 1, 118, 1, 118, 1, 119, 1, 119, 1, 119, 5, 119, 2730, 8, 119, 10, 119, 12, 119, 2733, 9, 119, 1, 120, 1, 120, 1, 120, 1, 120, 1, 120, 1, 120, 1, 120, 1, 120, 1, 120, 1, 120, 3, 120, 2745, 8, 120, 3, 120, 2747, 8, 120, 1, 120, 1, 120, 1, 120, 1, 120, 1, 120, 1, 120, 5, 120, 2755, 8, 120, 10, 120, 12, 120, 2758, 9, 120, 1, 121, 3, 121, 2761, 8, 121, 1, 121, 1, 121, 1, 121, 1, 121, 1, 121, 1, 121, 3, 121, 2769, 8, 121, 1, 121, 1, 121, 1, 121, 1, 121, 1, 121, 5, 121, 2776, 8, 121, 10, 121, 12, 121, 2779, 9, 121, 1, 121, 1, 121, 1, 121, 3, 121, 2784, 8, 121, 1, 121, 1, 121, 1, 121, 1, 121, 1, 121, 1, 121, 3, 121, 2792, 8, 121, 1, 121, 1, 121, 1, 121, 3, 121, 2797, 8, 121, 1, 121, 1, 121, 1, 121, 1, 121, 1, 121, 1, 121, 1, 121, 1, 121, 5, 121, 2807, 8, 121, 10, 121, 12, 121, 2810, 9, 121, 1, 121, 1, 121, 3, 121, 2814, 8, 121, 1, 121, 3, 121, 2817, 8, 121, 1, 121, 1, 121, 1, 121, 1, 121, 3, 121, 2823, 8, 121, 1, 121, 1, 121, 3, 121, 2827, 8, 121, 1, 121, 1, 121, 1, 121, 3, 121, 2832, 8, 121, 1, 121, 1, 121, 1, 121, 3, 121, 2837, 8, 121, 1, 121, 1, 121, 1, 121, 3, 121, 2842, 8, 121, 1, 122, 1, 122, 1, 122, 1, 122, 3, 122, 2848, 8, 122, 1, 122, 1, 122, 1, 122, 1, 122, 1, 122, 1, 122, 1, 122, 1, 122, 1, 122, 1, 122, 1, 122, 1, 122, 1, 122, 1, 122, 1, 122, 1, 122, 1, 122, 1, 122, 1, 122, 5, 122, 2869, 8, 122, 10, 122, 12, 122, 2872, 9, 122, 1, 123, 1, 123, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 4, 124, 2898, 8, 124, 11, 124, 12, 124, 2899, 1, 124, 1, 124, 3, 124, 2904, 8, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 4, 124, 2911, 8, 124, 11, 124, 12, 124, 2912, 1, 124, 1, 124, 3, 124, 2917, 8, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 5, 124, 2933, 8, 124, 10, 124, 12, 124, 2936, 9, 124, 3, 124, 2938, 8, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 3, 124, 2946, 8, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 3, 124, 2955, 8, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 3, 124, 2964, 8, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 4, 124, 2985, 8, 124, 11, 124, 12, 124, 2986, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 3, 124, 2998, 8, 124, 1, 124, 1, 124, 1, 124, 5, 124, 3003, 8, 124, 10, 124, 12, 124, 3006, 9, 124, 3, 124, 3008, 8, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 3, 124, 3017, 8, 124, 1, 124, 1, 124, 3, 124, 3021, 8, 124, 1, 124, 1, 124, 3, 124, 3025, 8, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 4, 124, 3035, 8, 124, 11, 124, 12, 124, 3036, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 3, 124, 3062, 8, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 3, 124, 3069, 8, 124, 1, 124, 3, 124, 3072, 8, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 3, 124, 3087, 8, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 3, 124, 3108, 8, 124, 1, 124, 1, 124, 3, 124, 3112, 8, 124, 3, 124, 3114, 8, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 5, 124, 3124, 8, 124, 10, 124, 12, 124, 3127, 9, 124, 1, 125, 1, 125, 1, 125, 1, 125, 1, 125, 1, 125, 1, 125, 1, 125, 1, 125, 1, 125, 4, 125, 3139, 8, 125, 11, 125, 12, 125, 3140, 3, 125, 3143, 8, 125, 1, 126, 1, 126, 1, 127, 1, 127, 1, 128, 1, 128, 1, 129, 1, 129, 1, 130, 1, 130, 1, 130, 3, 130, 3156, 8, 130, 1, 131, 1, 131, 3, 131, 3160, 8, 131, 1, 132, 1, 132, 1, 132, 4, 132, 3165, 8, 132, 11, 132, 12, 132, 3166, 1, 133, 1, 133, 1, 133, 3, 133, 3172, 8, 133, 1, 134, 1, 134, 1, 134, 1, 134, 1, 134, 1, 135, 3, 135, 3180, 8, 135, 1, 135, 1, 135, 1, 135, 3, 135, 3185, 8, 135, 1, 136, 1, 136, 1, 137, 1, 137, 1, 138, 1, 138, 1, 138, 3, 138, 3194, 8, 138, 1, 139, 1, 139, 1, 139, 1, 139, 1, 139, 1, 139, 1, 139, 1, 139, 1, 139, 1, 139, 1, 139, 1, 139, 1, 139, 1, 139, 1, 139, 3, 139, 3211, 8, 139, 1, 139, 1, 139, 3, 139, 3215, 8, 139, 1, 139, 1, 139, 1, 139, 1, 139, 3, 139, 3221, 8, 139, 1, 139, 1, 139, 1, 139, 1, 139, 3, 139, 3227, 8, 139, 1, 139, 1, 139, 1, 139, 1, 139, 1, 139, 5, 139, 3234, 8, 139, 10, 139, 12, 139, 3237, 9, 139, 1, 139, 3, 139, 3240, 8, 139, 3, 139, 3242, 8, 139, 1, 140, 1, 140, 1, 140, 5, 140, 3247, 8, 140, 10, 140, 12, 140, 3250, 9, 140, 1, 141, 1, 141, 1, 141, 5, 141, 3255, 8, 141, 10, 141, 12, 141, 3258, 9, 141, 1, 142, 1, 142, 1, 142, 1, 142, 1, 142, 3, 142, 3265, 8, 142, 1, 143, 1, 143, 1, 143, 1, 144, 1, 144, 1, 144, 5, 144, 3273, 8, 144, 10, 144, 12, 144, 3276, 9, 144, 1, 145, 1, 145, 1, 145, 1, 145, 3, 145, 3282, 8, 145, 1, 145, 3, 145, 3285, 8, 145, 1, 146, 1, 146, 1, 146, 5, 146, 3290, 8, 146, 10, 146, 12, 146, 3293, 9, 146, 1, 147, 1, 147, 1, 147, 5, 147, 3298, 8, 147, 10, 147, 12, 147, 3301, 9, 147, 1, 148, 1, 148, 1, 148, 1, 148, 1, 148, 3, 148, 3308, 8, 148, 1, 149, 1, 149, 1, 149, 1, 149, 1, 149, 1, 149, 1, 149, 1, 150, 1, 150, 1, 150, 5, 150, 3320, 8, 150, 10, 150, 12, 150, 3323, 9, 150, 1, 151, 1, 151, 3, 151, 3327, 8, 151, 1, 151, 1, 151, 1, 151, 3, 151, 3332, 8, 151, 1, 151, 3, 151, 3335, 8, 151, 1, 152, 1, 152, 1, 152, 1, 152, 1, 152, 1, 153, 1, 153, 1, 153, 1, 153, 5, 153, 3346, 8, 153, 10, 153, 12, 153, 3349, 9, 153, 1, 154, 1, 154, 1, 154, 1, 154, 1, 155, 1, 155, 1, 155, 1, 155, 1, 155, 1, 155, 1, 155, 1, 155, 1, 155, 1, 155, 1, 155, 5, 155, 3366, 8, 155, 10, 155, 12, 155, 3369, 9, 155, 1, 155, 1, 155, 1, 155, 1, 155, 1, 155, 5, 155, 3376, 8, 155, 10, 155, 12, 155, 3379, 9, 155, 3, 155, 3381, 8, 155, 1, 155, 1, 155, 1, 155, 1, 155, 1, 155, 5, 155, 3388, 8, 155, 10, 155, 12, 155, 3391, 9, 155, 3, 155, 3393, 8, 155, 3, 155, 3395, 8, 155, 1, 155, 3, 155, 3398, 8, 155, 1, 155, 3, 155, 3401, 8, 155, 1, 156, 1, 156, 1, 156, 1, 156, 1, 156, 1, 156, 1, 156, 1, 156, 1, 156, 1, 156, 1, 156, 1, 156, 1, 156, 1, 156, 1, 156, 1, 156, 3, 156, 3419, 8, 156, 1, 157, 1, 157, 1, 157, 1, 157, 1, 157, 1, 157, 1, 157, 3, 157, 3428, 8, 157, 1, 158, 1, 158, 1, 158, 5, 158, 3433, 8, 158, 10, 158, 12, 158, 3436, 9, 158, 1, 159, 1, 159, 1, 159, 1, 159, 3, 159, 3442, 8, 159, 1, 160, 1, 160, 1, 160, 5, 160, 3447, 8, 160, 10, 160, 12, 160, 3450, 9, 160, 1, 161, 1, 161, 1, 161, 1, 162, 1, 162, 4, 162, 3457, 8, 162, 11, 162, 12, 162, 3458, 1, 162, 3, 162, 3462, 8, 162, 1, 163, 1, 163, 3, 163, 3466, 8, 163, 1, 164, 1, 164, 1, 164, 1, 164, 3, 164, 3472, 8, 164, 1, 165, 1, 165, 1, 166, 1, 166, 1, 167, 3, 167, 3479, 8, 167, 1, 167, 1, 167, 3, 167, 3483, 8, 167, 1, 167, 1, 167, 3, 167, 3487, 8, 167, 1, 167, 1, 167, 3, 167, 3491, 8, 167, 1, 167, 1, 167, 3, 167, 3495, 8, 167, 1, 167, 1, 167, 3, 167, 3499, 8, 167, 1, 167, 1, 167, 3, 167, 3503, 8, 167, 1, 167, 1, 167, 3, 167, 3507, 8, 167, 1, 167, 1, 167, 3, 167, 3511, 8, 167, 1, 167, 1, 167, 3, 167, 3515, 8, 167, 1, 167, 3, 167, 3518, 8, 167, 1, 168, 1, 168, 1, 168, 1, 168, 1, 168, 1, 168, 1, 168, 1, 168, 1, 168, 1, 168, 1, 168, 3, 168, 3531, 8, 168, 1, 169, 1, 169, 1, 170, 1, 170, 3, 170, 3537, 8, 170, 1, 171, 1, 171, 3, 171, 3541, 8, 171, 1, 172, 1, 172, 1, 173, 1, 173, 1, 174, 1, 174, 1, 174, 9, 1000, 1067, 1075, 1092, 1106, 1115, 1124, 1133, 1177, 4, 88, 240, 244, 248, 175, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190, 192, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 222, 224, 226, 228, 230, 232, 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254, 256, 258, 260, 262, 264, 266, 268, 270, 272, 274, 276, 278, 280, 282, 284, 286, 288, 290, 292, 294, 296, 298, 300, 302, 304, 306, 308, 310, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 332, 334, 336, 338, 340, 342, 344, 346, 348, 0, 59, 2, 0, 69, 69, 202, 202, 2, 0, 30, 30, 219, 219, 2, 0, 107, 107, 122, 122, 1, 0, 43, 44, 2, 0, 258, 258, 296, 296, 2, 0, 11, 11, 35, 35, 5, 0, 40, 40, 52, 52, 93, 93, 106, 106, 152, 152, 1, 0, 74, 75, 2, 0, 93, 93, 106, 106, 3, 0, 8, 8, 82, 82, 255, 255, 2, 0, 8, 8, 146, 146, 3, 0, 65, 65, 166, 166, 231, 231, 3, 0, 66, 66, 167, 167, 232, 232, 4, 0, 87, 87, 130, 130, 240, 240, 284, 284, 2, 0, 21, 21, 74, 74, 2, 0, 101, 101, 137, 137, 2, 0, 257, 257, 295, 295, 2, 0, 256, 256, 267, 267, 2, 0, 55, 55, 226, 226, 2, 0, 89, 89, 123, 123, 2, 0, 10, 10, 79, 79, 2, 0, 335, 335, 337, 337, 1, 0, 142, 143, 3, 0, 10, 10, 16, 16, 244, 244, 3, 0, 96, 96, 277, 277, 286, 286, 2, 0, 316, 317, 321, 321, 2, 0, 81, 81, 318, 320, 2, 0, 316, 317, 324, 324, 11, 0, 61, 61, 63, 63, 117, 117, 157, 157, 159, 159, 161, 161, 163, 163, 204, 204, 229, 229, 298, 298, 305, 305, 3, 0, 57, 57, 59, 60, 292, 292, 2, 0, 67, 67, 268, 268, 2, 0, 68, 68, 269, 269, 2, 0, 32, 32, 279, 279, 2, 0, 120, 120, 218, 218, 1, 0, 253, 254, 2, 0, 4, 4, 107, 107, 2, 0, 4, 4, 103, 103, 3, 0, 25, 25, 140, 140, 272, 272, 1, 0, 193, 194, 1, 0, 308, 315, 2, 0, 81, 81, 316, 325, 4, 0, 14, 14, 122, 122, 172, 172, 181, 181, 2, 0, 96, 96, 277, 277, 1, 0, 316, 317, 7, 0, 61, 62, 117, 118, 157, 164, 168, 169, 229, 230, 298, 299, 305, 306, 6, 0, 61, 61, 117, 117, 161, 161, 163, 163, 229, 229, 305, 305, 2, 0, 163, 163, 305, 305, 4, 0, 61, 61, 117, 117, 161, 161, 229, 229, 3, 0, 117, 117, 161, 161, 229, 229, 2, 0, 80, 80, 190, 190, 2, 0, 182, 182, 245, 245, 2, 0, 102, 102, 199, 199, 2, 0, 331, 331, 342, 342, 1, 0, 336, 337, 2, 0, 82, 82, 239, 239, 1, 0, 330, 331, 51, 0, 8, 9, 11, 13, 15, 15, 17, 19, 21, 22, 24, 24, 26, 30, 33, 35, 37, 40, 42, 42, 44, 50, 52, 52, 55, 56, 61, 78, 80, 82, 86, 86, 88, 95, 98, 98, 100, 102, 105, 106, 109, 112, 115, 115, 117, 121, 123, 125, 127, 129, 131, 131, 134, 134, 136, 137, 139, 139, 142, 169, 171, 171, 174, 175, 179, 180, 183, 183, 185, 186, 188, 192, 195, 199, 201, 210, 212, 220, 222, 232, 234, 237, 239, 243, 245, 257, 259, 264, 267, 269, 271, 271, 273, 283, 287, 291, 294, 299, 302, 302, 305, 307, 16, 0, 15, 15, 54, 54, 87, 87, 108, 108, 126, 126, 130, 130, 135, 135, 138, 138, 141, 141, 170, 170, 177, 177, 221, 221, 234, 234, 240, 240, 284, 284, 293, 293, 17, 0, 8, 14, 16, 53, 55, 86, 88, 107, 109, 125, 127, 129, 131, 134, 136, 137, 139, 140, 142, 169, 171, 176, 178, 220, 222, 233, 235, 239, 241, 283, 285, 292, 294, 307, 4068, 0, 350, 1, 0, 0, 0, 2, 359, 1, 0, 0, 0, 4, 362, 1, 0, 0, 0, 6, 365, 1, 0, 0, 0, 8, 368, 1, 0, 0, 0, 10, 371, 1, 0, 0, 0, 12, 374, 1, 0, 0, 0, 14, 1180, 1, 0, 0, 0, 16, 1184, 1, 0, 0, 0, 18, 1186, 1, 0, 0, 0, 20, 1188, 1, 0, 0, 0, 22, 1358, 1, 0, 0, 0, 24, 1360, 1, 0, 0, 0, 26, 1377, 1, 0, 0, 0, 28, 1383, 1, 0, 0, 0, 30, 1395, 1, 0, 0, 0, 32, 1408, 1, 0, 0, 0, 34, 1411, 1, 0, 0, 0, 36, 1415, 1, 0, 0, 0, 38, 1490, 1, 0, 0, 0, 40, 1492, 1, 0, 0, 0, 42, 1496, 1, 0, 0, 0, 44, 1517, 1, 0, 0, 0, 46, 1519, 1, 0, 0, 0, 48, 1521, 1, 0, 0, 0, 50, 1528, 1, 0, 0, 0, 52, 1530, 1, 0, 0, 0, 54, 1538, 1, 0, 0, 0, 56, 1547, 1, 0, 0, 0, 58, 1558, 1, 0, 0, 0, 60, 1576, 1, 0, 0, 0, 62, 1579, 1, 0, 0, 0, 64, 1590, 1, 0, 0, 0, 66, 1606, 1, 0, 0, 0, 68, 1612, 1, 0, 0, 0, 70, 1614, 1, 0, 0, 0, 72, 1625, 1, 0, 0, 0, 74, 1642, 1, 0, 0, 0, 76, 1650, 1, 0, 0, 0, 78, 1652, 1, 0, 0, 0, 80, 1658, 1, 0, 0, 0, 82, 1717, 1, 0, 0, 0, 84, 1729, 1, 0, 0, 0, 86, 1781, 1, 0, 0, 0, 88, 1784, 1, 0, 0, 0, 90, 1807, 1, 0, 0, 0, 92, 1809, 1, 0, 0, 0, 94, 1817, 1, 0, 0, 0, 96, 1850, 1, 0, 0, 0, 98, 1896, 1, 0, 0, 0, 100, 1917, 1, 0, 0, 0, 102, 1949, 1, 0, 0, 0, 104, 1961, 1, 0, 0, 0, 106, 1964, 1, 0, 0, 0, 108, 1973, 1, 0, 0, 0, 110, 1987, 1, 0, 0, 0, 112, 2006, 1, 0, 0, 0, 114, 2026, 1, 0, 0, 0, 116, 2032, 1, 0, 0, 0, 118, 2034, 1, 0, 0, 0, 120, 2042, 1, 0, 0, 0, 122, 2046, 1, 0, 0, 0, 124, 2049, 1, 0, 0, 0, 126, 2052, 1, 0, 0, 0, 128, 2078, 1, 0, 0, 0, 130, 2080, 1, 0, 0, 0, 132, 2115, 1, 0, 0, 0, 134, 2156, 1, 0, 0, 0, 136, 2160, 1, 0, 0, 0, 138, 2187, 1, 0, 0, 0, 140, 2191, 1, 0, 0, 0, 142, 2206, 1, 0, 0, 0, 144, 2208, 1, 0, 0, 0, 146, 2238, 1, 0, 0, 0, 148, 2240, 1, 0, 0, 0, 150, 2247, 1, 0, 0, 0, 152, 2260, 1, 0, 0, 0, 154, 2265, 1, 0, 0, 0, 156, 2267, 1, 0, 0, 0, 158, 2282, 1, 0, 0, 0, 160, 2306, 1, 0, 0, 0, 162, 2319, 1, 0, 0, 0, 164, 2321, 1, 0, 0, 0, 166, 2323, 1, 0, 0, 0, 168, 2327, 1, 0, 0, 0, 170, 2330, 1, 0, 0, 0, 172, 2334, 1, 0, 0, 0, 174, 2366, 1, 0, 0, 0, 176, 2369, 1, 0, 0, 0, 178, 2381, 1, 0, 0, 0, 180, 2400, 1, 0, 0, 0, 182, 2426, 1, 0, 0, 0, 184, 2432, 1, 0, 0, 0, 186, 2434, 1, 0, 0, 0, 188, 2470, 1, 0, 0, 0, 190, 2472, 1, 0, 0, 0, 192, 2476, 1, 0, 0, 0, 194, 2484, 1, 0, 0, 0, 196, 2495, 1, 0, 0, 0, 198, 2499, 1, 0, 0, 0, 200, 2510, 1, 0, 0, 0, 202, 2541, 1, 0, 0, 0, 204, 2543, 1, 0, 0, 0, 206, 2554, 1, 0, 0, 0, 208, 2576, 1, 0, 0, 0, 210, 2627, 1, 0, 0, 0, 212, 2629, 1, 0, 0, 0, 214, 2637, 1, 0, 0, 0, 216, 2645, 1, 0, 0, 0, 218, 2653, 1, 0, 0, 0, 220, 2661, 1, 0, 0, 0, 222, 2668, 1, 0, 0, 0, 224, 2672, 1, 0, 0, 0, 226, 2682, 1, 0, 0, 0, 228, 2690, 1, 0, 0, 0, 230, 2703, 1, 0, 0, 0, 232, 2718, 1, 0, 0, 0, 234, 2722, 1, 0, 0, 0, 236, 2724, 1, 0, 0, 0, 238, 2726, 1, 0, 0, 0, 240, 2746, 1, 0, 0, 0, 242, 2841, 1, 0, 0, 0, 244, 2847, 1, 0, 0, 0, 246, 2873, 1, 0, 0, 0, 248, 3113, 1, 0, 0, 0, 250, 3142, 1, 0, 0, 0, 252, 3144, 1, 0, 0, 0, 254, 3146, 1, 0, 0, 0, 256, 3148, 1, 0, 0, 0, 258, 3150, 1, 0, 0, 0, 260, 3152, 1, 0, 0, 0, 262, 3157, 1, 0, 0, 0, 264, 3164, 1, 0, 0, 0, 266, 3168, 1, 0, 0, 0, 268, 3173, 1, 0, 0, 0, 270, 3179, 1, 0, 0, 0, 272, 3186, 1, 0, 0, 0, 274, 3188, 1, 0, 0, 0, 276, 3193, 1, 0, 0, 0, 278, 3241, 1, 0, 0, 0, 280, 3243, 1, 0, 0, 0, 282, 3251, 1, 0, 0, 0, 284, 3264, 1, 0, 0, 0, 286, 3266, 1, 0, 0, 0, 288, 3269, 1, 0, 0, 0, 290, 3277, 1, 0, 0, 0, 292, 3286, 1, 0, 0, 0, 294, 3294, 1, 0, 0, 0, 296, 3307, 1, 0, 0, 0, 298, 3309, 1, 0, 0, 0, 300, 3316, 1, 0, 0, 0, 302, 3324, 1, 0, 0, 0, 304, 3336, 1, 0, 0, 0, 306, 3341, 1, 0, 0, 0, 308, 3350, 1, 0, 0, 0, 310, 3400, 1, 0, 0, 0, 312, 3418, 1, 0, 0, 0, 314, 3427, 1, 0, 0, 0, 316, 3429, 1, 0, 0, 0, 318, 3441, 1, 0, 0, 0, 320, 3443, 1, 0, 0, 0, 322, 3451, 1, 0, 0, 0, 324, 3461, 1, 0, 0, 0, 326, 3465, 1, 0, 0, 0, 328, 3471, 1, 0, 0, 0, 330, 3473, 1, 0, 0, 0, 332, 3475, 1, 0, 0, 0, 334, 3517, 1, 0, 0, 0, 336, 3530, 1, 0, 0, 0, 338, 3532, 1, 0, 0, 0, 340, 3536, 1, 0, 0, 0, 342, 3540, 1, 0, 0, 0, 344, 3542, 1, 0, 0, 0, 346, 3544, 1, 0, 0, 0, 348, 3546, 1, 0, 0, 0, 350, 354, 3, 14, 7, 0, 351, 353, 5, 1, 0, 0, 352, 351, 1, 0, 0, 0, 353, 356, 1, 0, 0, 0, 354, 352, 1, 0, 0, 0, 354, 355, 1, 0, 0, 0, 355, 357, 1, 0, 0, 0, 356, 354, 1, 0, 0, 0, 357, 358, 5, 0, 0, 1, 358, 1, 1, 0, 0, 0, 359, 360, 3, 224, 112, 0, 360, 361, 5, 0, 0, 1, 361, 3, 1, 0, 0, 0, 362, 363, 3, 220, 110, 0, 363, 364, 5, 0, 0, 1, 364, 5, 1, 0, 0, 0, 365, 366, 3, 214, 107, 0, 366, 367, 5, 0, 0, 1, 367, 7, 1, 0, 0, 0, 368, 369, 3, 222, 111, 0, 369, 370, 5, 0, 0, 1, 370, 9, 1, 0, 0, 0, 371, 372, 3, 278, 139, 0, 372, 373, 5, 0, 0, 1, 373, 11, 1, 0, 0, 0, 374, 375, 3, 288, 144, 0, 375, 376, 5, 0, 0, 1, 376, 13, 1, 0, 0, 0, 377, 1181, 3, 36, 18, 0, 378, 380, 3, 54, 27, 0, 379, 378, 1, 0, 0, 0, 379, 380, 1, 0, 0, 0, 380, 381, 1, 0, 0, 0, 381, 1181, 3, 82, 41, 0, 382, 383, 5, 291, 0, 0, 383, 1181, 3, 214, 107, 0, 384, 385, 5, 291, 0, 0, 385, 386, 3, 46, 23, 0, 386, 387, 3, 214, 107, 0, 387, 1181, 1, 0, 0, 0, 388, 389, 5, 239, 0, 0, 389, 392, 5, 33, 0, 0, 390, 393, 3, 326, 163, 0, 391, 393, 3, 338, 169, 0, 392, 390, 1, 0, 0, 0, 392, 391, 1, 0, 0, 0, 393, 1181, 1, 0, 0, 0, 394, 395, 5, 53, 0, 0, 395, 399, 3, 46, 23, 0, 396, 397, 5, 119, 0, 0, 397, 398, 5, 172, 0, 0, 398, 400, 5, 90, 0, 0, 399, 396, 1, 0, 0, 0, 399, 400, 1, 0, 0, 0, 400, 401, 1, 0, 0, 0, 401, 409, 3, 214, 107, 0, 402, 408, 3, 34, 17, 0, 403, 408, 3, 32, 16, 0, 404, 405, 5, 303, 0, 0, 405, 406, 7, 0, 0, 0, 406, 408, 3, 62, 31, 0, 407, 402, 1, 0, 0, 0, 407, 403, 1, 0, 0, 0, 407, 404, 1, 0, 0, 0, 408, 411, 1, 0, 0, 0, 409, 407, 1, 0, 0, 0, 409, 410, 1, 0, 0, 0, 410, 1181, 1, 0, 0, 0, 411, 409, 1, 0, 0, 0, 412, 413, 5, 11, 0, 0, 413, 414, 3, 46, 23, 0, 414, 415, 3, 214, 107, 0, 415, 416, 5, 239, 0, 0, 416, 417, 7, 0, 0, 0, 417, 418, 3, 62, 31, 0, 418, 1181, 1, 0, 0, 0, 419, 420, 5, 11, 0, 0, 420, 421, 3, 46, 23, 0, 421, 422, 3, 214, 107, 0, 422, 423, 5, 239, 0, 0, 423, 424, 3, 32, 16, 0, 424, 1181, 1, 0, 0, 0, 425, 426, 5, 82, 0, 0, 426, 429, 3, 46, 23, 0, 427, 428, 5, 119, 0, 0, 428, 430, 5, 90, 0, 0, 429, 427, 1, 0, 0, 0, 429, 430, 1, 0, 0, 0, 430, 431, 1, 0, 0, 0, 431, 433, 3, 214, 107, 0, 432, 434, 7, 1, 0, 0, 433, 432, 1, 0, 0, 0, 433, 434, 1, 0, 0, 0, 434, 1181, 1, 0, 0, 0, 435, 436, 5, 242, 0, 0, 436, 439, 3, 48, 24, 0, 437, 438, 7, 2, 0, 0, 438, 440, 3, 214, 107, 0, 439, 437, 1, 0, 0, 0, 439, 440, 1, 0, 0, 0, 440, 445, 1, 0, 0, 0, 441, 443, 5, 142, 0, 0, 442, 441, 1, 0, 0, 0, 442, 443, 1, 0, 0, 0, 443, 444, 1, 0, 0, 0, 444, 446, 3, 338, 169, 0, 445, 442, 1, 0, 0, 0, 445, 446, 1, 0, 0, 0, 446, 1181, 1, 0, 0, 0, 447, 452, 3, 24, 12, 0, 448, 449, 5, 2, 0, 0, 449, 450, 3, 292, 146, 0, 450, 451, 5, 3, 0, 0, 451, 453, 1, 0, 0, 0, 452, 448, 1, 0, 0, 0, 452, 453, 1, 0, 0, 0, 453, 455, 1, 0, 0, 0, 454, 456, 3, 58, 29, 0, 455, 454, 1, 0, 0, 0, 455, 456, 1, 0, 0, 0, 456, 457, 1, 0, 0, 0, 457, 462, 3, 60, 30, 0, 458, 460, 5, 20, 0, 0, 459, 458, 1, 0, 0, 0, 459, 460, 1, 0, 0, 0, 460, 461, 1, 0, 0, 0, 461, 463, 3, 36, 18, 0, 462, 459, 1, 0, 0, 0, 462, 463, 1, 0, 0, 0, 463, 1181, 1, 0, 0, 0, 464, 465, 5, 53, 0, 0, 465, 469, 5, 258, 0, 0, 466, 467, 5, 119, 0, 0, 467, 468, 5, 172, 0, 0, 468, 470, 5, 90, 0, 0, 469, 466, 1, 0, 0, 0, 469, 470, 1, 0, 0, 0, 470, 471, 1, 0, 0, 0, 471, 472, 3, 220, 110, 0, 472, 473, 5, 142, 0, 0, 473, 482, 3, 220, 110, 0, 474, 481, 3, 58, 29, 0, 475, 481, 3, 210, 105, 0, 476, 481, 3, 74, 37, 0, 477, 481, 3, 32, 16, 0, 478, 479, 5, 262, 0, 0, 479, 481, 3, 62, 31, 0, 480, 474, 1, 0, 0, 0, 480, 475, 1, 0, 0, 0, 480, 476, 1, 0, 0, 0, 480, 477, 1, 0, 0, 0, 480, 478, 1, 0, 0, 0, 481, 484, 1, 0, 0, 0, 482, 480, 1, 0, 0, 0, 482, 483, 1, 0, 0, 0, 483, 1181, 1, 0, 0, 0, 484, 482, 1, 0, 0, 0, 485, 490, 3, 26, 13, 0, 486, 487, 5, 2, 0, 0, 487, 488, 3, 292, 146, 0, 488, 489, 5, 3, 0, 0, 489, 491, 1, 0, 0, 0, 490, 486, 1, 0, 0, 0, 490, 491, 1, 0, 0, 0, 491, 493, 1, 0, 0, 0, 492, 494, 3, 58, 29, 0, 493, 492, 1, 0, 0, 0, 493, 494, 1, 0, 0, 0, 494, 495, 1, 0, 0, 0, 495, 500, 3, 60, 30, 0, 496, 498, 5, 20, 0, 0, 497, 496, 1, 0, 0, 0, 497, 498, 1, 0, 0, 0, 498, 499, 1, 0, 0, 0, 499, 501, 3, 36, 18, 0, 500, 497, 1, 0, 0, 0, 500, 501, 1, 0, 0, 0, 501, 1181, 1, 0, 0, 0, 502, 503, 5, 13, 0, 0, 503, 504, 5, 258, 0, 0, 504, 506, 3, 214, 107, 0, 505, 507, 3, 42, 21, 0, 506, 505, 1, 0, 0, 0, 506, 507, 1, 0, 0, 0, 507, 508, 1, 0, 0, 0, 508, 509, 5, 49, 0, 0, 509, 517, 5, 249, 0, 0, 510, 518, 3, 326, 163, 0, 511, 512, 5, 103, 0, 0, 512, 513, 5, 44, 0, 0, 513, 518, 3, 192, 96, 0, 514, 515, 5, 103, 0, 0, 515, 516, 5, 10, 0, 0, 516, 518, 5, 44, 0, 0, 517, 510, 1, 0, 0, 0, 517, 511, 1, 0, 0, 0, 517, 514, 1, 0, 0, 0, 517, 518, 1, 0, 0, 0, 518, 1181, 1, 0, 0, 0, 519, 520, 5, 13, 0, 0, 520, 523, 5, 259, 0, 0, 521, 522, 7, 2, 0, 0, 522, 524, 3, 214, 107, 0, 523, 521, 1, 0, 0, 0, 523, 524, 1, 0, 0, 0, 524, 525, 1, 0, 0, 0, 525, 526, 5, 49, 0, 0, 526, 528, 5, 249, 0, 0, 527, 529, 3, 326, 163, 0, 528, 527, 1, 0, 0, 0, 528, 529, 1, 0, 0, 0, 529, 1181, 1, 0, 0, 0, 530, 531, 5, 11, 0, 0, 531, 532, 5, 258, 0, 0, 532, 533, 3, 214, 107, 0, 533, 534, 5, 8, 0, 0, 534, 535, 7, 3, 0, 0, 535, 536, 3, 280, 140, 0, 536, 1181, 1, 0, 0, 0, 537, 538, 5, 11, 0, 0, 538, 539, 5, 258, 0, 0, 539, 540, 3, 214, 107, 0, 540, 541, 5, 8, 0, 0, 541, 542, 7, 3, 0, 0, 542, 543, 5, 2, 0, 0, 543, 544, 3, 280, 140, 0, 544, 545, 5, 3, 0, 0, 545, 1181, 1, 0, 0, 0, 546, 547, 5, 11, 0, 0, 547, 548, 5, 258, 0, 0, 548, 549, 3, 214, 107, 0, 549, 550, 5, 213, 0, 0, 550, 551, 5, 43, 0, 0, 551, 552, 3, 214, 107, 0, 552, 553, 5, 270, 0, 0, 553, 554, 3, 322, 161, 0, 554, 1181, 1, 0, 0, 0, 555, 556, 5, 11, 0, 0, 556, 557, 5, 258, 0, 0, 557, 558, 3, 214, 107, 0, 558, 559, 5, 82, 0, 0, 559, 562, 7, 3, 0, 0, 560, 561, 5, 119, 0, 0, 561, 563, 5, 90, 0, 0, 562, 560, 1, 0, 0, 0, 562, 563, 1, 0, 0, 0, 563, 564, 1, 0, 0, 0, 564, 565, 5, 2, 0, 0, 565, 566, 3, 212, 106, 0, 566, 567, 5, 3, 0, 0, 567, 1181, 1, 0, 0, 0, 568, 569, 5, 11, 0, 0, 569, 570, 5, 258, 0, 0, 570, 571, 3, 214, 107, 0, 571, 572, 5, 82, 0, 0, 572, 575, 7, 3, 0, 0, 573, 574, 5, 119, 0, 0, 574, 576, 5, 90, 0, 0, 575, 573, 1, 0, 0, 0, 575, 576, 1, 0, 0, 0, 576, 577, 1, 0, 0, 0, 577, 578, 3, 212, 106, 0, 578, 1181, 1, 0, 0, 0, 579, 580, 5, 11, 0, 0, 580, 581, 7, 4, 0, 0, 581, 582, 3, 214, 107, 0, 582, 583, 5, 213, 0, 0, 583, 584, 5, 270, 0, 0, 584, 585, 3, 214, 107, 0, 585, 1181, 1, 0, 0, 0, 586, 587, 5, 11, 0, 0, 587, 588, 7, 4, 0, 0, 588, 589, 3, 214, 107, 0, 589, 590, 5, 239, 0, 0, 590, 591, 5, 262, 0, 0, 591, 592, 3, 62, 31, 0, 592, 1181, 1, 0, 0, 0, 593, 594, 5, 11, 0, 0, 594, 595, 7, 4, 0, 0, 595, 596, 3, 214, 107, 0, 596, 597, 5, 289, 0, 0, 597, 600, 5, 262, 0, 0, 598, 599, 5, 119, 0, 0, 599, 601, 5, 90, 0, 0, 600, 598, 1, 0, 0, 0, 600, 601, 1, 0, 0, 0, 601, 602, 1, 0, 0, 0, 602, 603, 3, 62, 31, 0, 603, 1181, 1, 0, 0, 0, 604, 605, 5, 11, 0, 0, 605, 606, 5, 258, 0, 0, 606, 607, 3, 214, 107, 0, 607, 609, 7, 5, 0, 0, 608, 610, 5, 43, 0, 0, 609, 608, 1, 0, 0, 0, 609, 610, 1, 0, 0, 0, 610, 611, 1, 0, 0, 0, 611, 613, 3, 214, 107, 0, 612, 614, 3, 336, 168, 0, 613, 612, 1, 0, 0, 0, 613, 614, 1, 0, 0, 0, 614, 1181, 1, 0, 0, 0, 615, 616, 5, 11, 0, 0, 616, 617, 5, 258, 0, 0, 617, 619, 3, 214, 107, 0, 618, 620, 3, 42, 21, 0, 619, 618, 1, 0, 0, 0, 619, 620, 1, 0, 0, 0, 620, 621, 1, 0, 0, 0, 621, 623, 5, 35, 0, 0, 622, 624, 5, 43, 0, 0, 623, 622, 1, 0, 0, 0, 623, 624, 1, 0, 0, 0, 624, 625, 1, 0, 0, 0, 625, 626, 3, 214, 107, 0, 626, 628, 3, 290, 145, 0, 627, 629, 3, 276, 138, 0, 628, 627, 1, 0, 0, 0, 628, 629, 1, 0, 0, 0, 629, 1181, 1, 0, 0, 0, 630, 631, 5, 11, 0, 0, 631, 632, 5, 258, 0, 0, 632, 634, 3, 214, 107, 0, 633, 635, 3, 42, 21, 0, 634, 633, 1, 0, 0, 0, 634, 635, 1, 0, 0, 0, 635, 636, 1, 0, 0, 0, 636, 637, 5, 216, 0, 0, 637, 638, 5, 44, 0, 0, 638, 639, 5, 2, 0, 0, 639, 640, 3, 280, 140, 0, 640, 641, 5, 3, 0, 0, 641, 1181, 1, 0, 0, 0, 642, 643, 5, 11, 0, 0, 643, 644, 5, 258, 0, 0, 644, 646, 3, 214, 107, 0, 645, 647, 3, 42, 21, 0, 646, 645, 1, 0, 0, 0, 646, 647, 1, 0, 0, 0, 647, 648, 1, 0, 0, 0, 648, 649, 5, 239, 0, 0, 649, 650, 5, 236, 0, 0, 650, 654, 3, 338, 169, 0, 651, 652, 5, 303, 0, 0, 652, 653, 5, 237, 0, 0, 653, 655, 3, 62, 31, 0, 654, 651, 1, 0, 0, 0, 654, 655, 1, 0, 0, 0, 655, 1181, 1, 0, 0, 0, 656, 657, 5, 11, 0, 0, 657, 658, 5, 258, 0, 0, 658, 660, 3, 214, 107, 0, 659, 661, 3, 42, 21, 0, 660, 659, 1, 0, 0, 0, 660, 661, 1, 0, 0, 0, 661, 662, 1, 0, 0, 0, 662, 663, 5, 239, 0, 0, 663, 664, 5, 237, 0, 0, 664, 665, 3, 62, 31, 0, 665, 1181, 1, 0, 0, 0, 666, 667, 5, 11, 0, 0, 667, 668, 7, 4, 0, 0, 668, 669, 3, 214, 107, 0, 669, 673, 5, 8, 0, 0, 670, 671, 5, 119, 0, 0, 671, 672, 5, 172, 0, 0, 672, 674, 5, 90, 0, 0, 673, 670, 1, 0, 0, 0, 673, 674, 1, 0, 0, 0, 674, 676, 1, 0, 0, 0, 675, 677, 3, 40, 20, 0, 676, 675, 1, 0, 0, 0, 677, 678, 1, 0, 0, 0, 678, 676, 1, 0, 0, 0, 678, 679, 1, 0, 0, 0, 679, 1181, 1, 0, 0, 0, 680, 681, 5, 11, 0, 0, 681, 682, 5, 258, 0, 0, 682, 683, 3, 214, 107, 0, 683, 684, 3, 42, 21, 0, 684, 685, 5, 213, 0, 0, 685, 686, 5, 270, 0, 0, 686, 687, 3, 42, 21, 0, 687, 1181, 1, 0, 0, 0, 688, 689, 5, 11, 0, 0, 689, 690, 7, 4, 0, 0, 690, 691, 3, 214, 107, 0, 691, 694, 5, 82, 0, 0, 692, 693, 5, 119, 0, 0, 693, 695, 5, 90, 0, 0, 694, 692, 1, 0, 0, 0, 694, 695, 1, 0, 0, 0, 695, 696, 1, 0, 0, 0, 696, 701, 3, 42, 21, 0, 697, 698, 5, 4, 0, 0, 698, 700, 3, 42, 21, 0, 699, 697, 1, 0, 0, 0, 700, 703, 1, 0, 0, 0, 701, 699, 1, 0, 0, 0, 701, 702, 1, 0, 0, 0, 702, 705, 1, 0, 0, 0, 703, 701, 1, 0, 0, 0, 704, 706, 5, 203, 0, 0, 705, 704, 1, 0, 0, 0, 705, 706, 1, 0, 0, 0, 706, 1181, 1, 0, 0, 0, 707, 708, 5, 11, 0, 0, 708, 709, 5, 258, 0, 0, 709, 711, 3, 214, 107, 0, 710, 712, 3, 42, 21, 0, 711, 710, 1, 0, 0, 0, 711, 712, 1, 0, 0, 0, 712, 713, 1, 0, 0, 0, 713, 714, 5, 239, 0, 0, 714, 715, 3, 32, 16, 0, 715, 1181, 1, 0, 0, 0, 716, 717, 5, 11, 0, 0, 717, 718, 5, 258, 0, 0, 718, 719, 3, 214, 107, 0, 719, 720, 5, 209, 0, 0, 720, 721, 5, 192, 0, 0, 721, 1181, 1, 0, 0, 0, 722, 723, 5, 82, 0, 0, 723, 726, 5, 258, 0, 0, 724, 725, 5, 119, 0, 0, 725, 727, 5, 90, 0, 0, 726, 724, 1, 0, 0, 0, 726, 727, 1, 0, 0, 0, 727, 728, 1, 0, 0, 0, 728, 730, 3, 214, 107, 0, 729, 731, 5, 203, 0, 0, 730, 729, 1, 0, 0, 0, 730, 731, 1, 0, 0, 0, 731, 1181, 1, 0, 0, 0, 732, 733, 5, 82, 0, 0, 733, 736, 5, 296, 0, 0, 734, 735, 5, 119, 0, 0, 735, 737, 5, 90, 0, 0, 736, 734, 1, 0, 0, 0, 736, 737, 1, 0, 0, 0, 737, 738, 1, 0, 0, 0, 738, 1181, 3, 214, 107, 0, 739, 742, 5, 53, 0, 0, 740, 741, 5, 181, 0, 0, 741, 743, 5, 216, 0, 0, 742, 740, 1, 0, 0, 0, 742, 743, 1, 0, 0, 0, 743, 748, 1, 0, 0, 0, 744, 746, 5, 112, 0, 0, 745, 744, 1, 0, 0, 0, 745, 746, 1, 0, 0, 0, 746, 747, 1, 0, 0, 0, 747, 749, 5, 263, 0, 0, 748, 745, 1, 0, 0, 0, 748, 749, 1, 0, 0, 0, 749, 750, 1, 0, 0, 0, 750, 754, 5, 296, 0, 0, 751, 752, 5, 119, 0, 0, 752, 753, 5, 172, 0, 0, 753, 755, 5, 90, 0, 0, 754, 751, 1, 0, 0, 0, 754, 755, 1, 0, 0, 0, 755, 756, 1, 0, 0, 0, 756, 758, 3, 214, 107, 0, 757, 759, 3, 198, 99, 0, 758, 757, 1, 0, 0, 0, 758, 759, 1, 0, 0, 0, 759, 768, 1, 0, 0, 0, 760, 767, 3, 34, 17, 0, 761, 762, 5, 191, 0, 0, 762, 763, 5, 177, 0, 0, 763, 767, 3, 190, 95, 0, 764, 765, 5, 262, 0, 0, 765, 767, 3, 62, 31, 0, 766, 760, 1, 0, 0, 0, 766, 761, 1, 0, 0, 0, 766, 764, 1, 0, 0, 0, 767, 770, 1, 0, 0, 0, 768, 766, 1, 0, 0, 0, 768, 769, 1, 0, 0, 0, 769, 771, 1, 0, 0, 0, 770, 768, 1, 0, 0, 0, 771, 772, 5, 20, 0, 0, 772, 773, 3, 36, 18, 0, 773, 1181, 1, 0, 0, 0, 774, 777, 5, 53, 0, 0, 775, 776, 5, 181, 0, 0, 776, 778, 5, 216, 0, 0, 777, 775, 1, 0, 0, 0, 777, 778, 1, 0, 0, 0, 778, 780, 1, 0, 0, 0, 779, 781, 5, 112, 0, 0, 780, 779, 1, 0, 0, 0, 780, 781, 1, 0, 0, 0, 781, 782, 1, 0, 0, 0, 782, 783, 5, 263, 0, 0, 783, 784, 5, 296, 0, 0, 784, 789, 3, 220, 110, 0, 785, 786, 5, 2, 0, 0, 786, 787, 3, 288, 144, 0, 787, 788, 5, 3, 0, 0, 788, 790, 1, 0, 0, 0, 789, 785, 1, 0, 0, 0, 789, 790, 1, 0, 0, 0, 790, 791, 1, 0, 0, 0, 791, 794, 3, 58, 29, 0, 792, 793, 5, 180, 0, 0, 793, 795, 3, 62, 31, 0, 794, 792, 1, 0, 0, 0, 794, 795, 1, 0, 0, 0, 795, 1181, 1, 0, 0, 0, 796, 797, 5, 11, 0, 0, 797, 798, 5, 296, 0, 0, 798, 800, 3, 214, 107, 0, 799, 801, 5, 20, 0, 0, 800, 799, 1, 0, 0, 0, 800, 801, 1, 0, 0, 0, 801, 802, 1, 0, 0, 0, 802, 803, 3, 36, 18, 0, 803, 1181, 1, 0, 0, 0, 804, 807, 5, 53, 0, 0, 805, 806, 5, 181, 0, 0, 806, 808, 5, 216, 0, 0, 807, 805, 1, 0, 0, 0, 807, 808, 1, 0, 0, 0, 808, 810, 1, 0, 0, 0, 809, 811, 5, 263, 0, 0, 810, 809, 1, 0, 0, 0, 810, 811, 1, 0, 0, 0, 811, 812, 1, 0, 0, 0, 812, 816, 5, 109, 0, 0, 813, 814, 5, 119, 0, 0, 814, 815, 5, 172, 0, 0, 815, 817, 5, 90, 0, 0, 816, 813, 1, 0, 0, 0, 816, 817, 1, 0, 0, 0, 817, 818, 1, 0, 0, 0, 818, 819, 3, 214, 107, 0, 819, 820, 5, 20, 0, 0, 820, 830, 3, 338, 169, 0, 821, 822, 5, 293, 0, 0, 822, 827, 3, 80, 40, 0, 823, 824, 5, 4, 0, 0, 824, 826, 3, 80, 40, 0, 825, 823, 1, 0, 0, 0, 826, 829, 1, 0, 0, 0, 827, 825, 1, 0, 0, 0, 827, 828, 1, 0, 0, 0, 828, 831, 1, 0, 0, 0, 829, 827, 1, 0, 0, 0, 830, 821, 1, 0, 0, 0, 830, 831, 1, 0, 0, 0, 831, 1181, 1, 0, 0, 0, 832, 834, 5, 82, 0, 0, 833, 835, 5, 263, 0, 0, 834, 833, 1, 0, 0, 0, 834, 835, 1, 0, 0, 0, 835, 836, 1, 0, 0, 0, 836, 839, 5, 109, 0, 0, 837, 838, 5, 119, 0, 0, 838, 840, 5, 90, 0, 0, 839, 837, 1, 0, 0, 0, 839, 840, 1, 0, 0, 0, 840, 841, 1, 0, 0, 0, 841, 1181, 3, 214, 107, 0, 842, 844, 5, 91, 0, 0, 843, 845, 7, 6, 0, 0, 844, 843, 1, 0, 0, 0, 844, 845, 1, 0, 0, 0, 845, 846, 1, 0, 0, 0, 846, 1181, 3, 14, 7, 0, 847, 848, 5, 242, 0, 0, 848, 851, 5, 259, 0, 0, 849, 850, 7, 2, 0, 0, 850, 852, 3, 214, 107, 0, 851, 849, 1, 0, 0, 0, 851, 852, 1, 0, 0, 0, 852, 857, 1, 0, 0, 0, 853, 855, 5, 142, 0, 0, 854, 853, 1, 0, 0, 0, 854, 855, 1, 0, 0, 0, 855, 856, 1, 0, 0, 0, 856, 858, 3, 338, 169, 0, 857, 854, 1, 0, 0, 0, 857, 858, 1, 0, 0, 0, 858, 1181, 1, 0, 0, 0, 859, 860, 5, 242, 0, 0, 860, 861, 5, 258, 0, 0, 861, 864, 5, 93, 0, 0, 862, 863, 7, 2, 0, 0, 863, 865, 3, 214, 107, 0, 864, 862, 1, 0, 0, 0, 864, 865, 1, 0, 0, 0, 865, 866, 1, 0, 0, 0, 866, 867, 5, 142, 0, 0, 867, 869, 3, 338, 169, 0, 868, 870, 3, 42, 21, 0, 869, 868, 1, 0, 0, 0, 869, 870, 1, 0, 0, 0, 870, 1181, 1, 0, 0, 0, 871, 872, 5, 242, 0, 0, 872, 873, 5, 262, 0, 0, 873, 878, 3, 214, 107, 0, 874, 875, 5, 2, 0, 0, 875, 876, 3, 66, 33, 0, 876, 877, 5, 3, 0, 0, 877, 879, 1, 0, 0, 0, 878, 874, 1, 0, 0, 0, 878, 879, 1, 0, 0, 0, 879, 1181, 1, 0, 0, 0, 880, 881, 5, 242, 0, 0, 881, 882, 5, 44, 0, 0, 882, 883, 7, 2, 0, 0, 883, 886, 3, 214, 107, 0, 884, 885, 7, 2, 0, 0, 885, 887, 3, 214, 107, 0, 886, 884, 1, 0, 0, 0, 886, 887, 1, 0, 0, 0, 887, 1181, 1, 0, 0, 0, 888, 889, 5, 242, 0, 0, 889, 892, 5, 297, 0, 0, 890, 891, 7, 2, 0, 0, 891, 893, 3, 214, 107, 0, 892, 890, 1, 0, 0, 0, 892, 893, 1, 0, 0, 0, 893, 898, 1, 0, 0, 0, 894, 896, 5, 142, 0, 0, 895, 894, 1, 0, 0, 0, 895, 896, 1, 0, 0, 0, 896, 897, 1, 0, 0, 0, 897, 899, 3, 338, 169, 0, 898, 895, 1, 0, 0, 0, 898, 899, 1, 0, 0, 0, 899, 1181, 1, 0, 0, 0, 900, 901, 5, 242, 0, 0, 901, 902, 5, 192, 0, 0, 902, 904, 3, 214, 107, 0, 903, 905, 3, 42, 21, 0, 904, 903, 1, 0, 0, 0, 904, 905, 1, 0, 0, 0, 905, 1181, 1, 0, 0, 0, 906, 908, 5, 242, 0, 0, 907, 909, 3, 326, 163, 0, 908, 907, 1, 0, 0, 0, 908, 909, 1, 0, 0, 0, 909, 910, 1, 0, 0, 0, 910, 913, 5, 110, 0, 0, 911, 912, 7, 2, 0, 0, 912, 914, 3, 214, 107, 0, 913, 911, 1, 0, 0, 0, 913, 914, 1, 0, 0, 0, 914, 922, 1, 0, 0, 0, 915, 917, 5, 142, 0, 0, 916, 915, 1, 0, 0, 0, 916, 917, 1, 0, 0, 0, 917, 920, 1, 0, 0, 0, 918, 921, 3, 214, 107, 0, 919, 921, 3, 338, 169, 0, 920, 918, 1, 0, 0, 0, 920, 919, 1, 0, 0, 0, 921, 923, 1, 0, 0, 0, 922, 916, 1, 0, 0, 0, 922, 923, 1, 0, 0, 0, 923, 1181, 1, 0, 0, 0, 924, 925, 5, 242, 0, 0, 925, 926, 5, 53, 0, 0, 926, 927, 5, 258, 0, 0, 927, 930, 3, 214, 107, 0, 928, 929, 5, 20, 0, 0, 929, 931, 5, 236, 0, 0, 930, 928, 1, 0, 0, 0, 930, 931, 1, 0, 0, 0, 931, 1181, 1, 0, 0, 0, 932, 933, 5, 242, 0, 0, 933, 934, 5, 56, 0, 0, 934, 1181, 3, 46, 23, 0, 935, 936, 5, 242, 0, 0, 936, 941, 5, 34, 0, 0, 937, 939, 5, 142, 0, 0, 938, 937, 1, 0, 0, 0, 938, 939, 1, 0, 0, 0, 939, 940, 1, 0, 0, 0, 940, 942, 3, 338, 169, 0, 941, 938, 1, 0, 0, 0, 941, 942, 1, 0, 0, 0, 942, 1181, 1, 0, 0, 0, 943, 944, 7, 7, 0, 0, 944, 946, 5, 109, 0, 0, 945, 947, 5, 93, 0, 0, 946, 945, 1, 0, 0, 0, 946, 947, 1, 0, 0, 0, 947, 948, 1, 0, 0, 0, 948, 1181, 3, 50, 25, 0, 949, 950, 7, 7, 0, 0, 950, 952, 3, 46, 23, 0, 951, 953, 5, 93, 0, 0, 952, 951, 1, 0, 0, 0, 952, 953, 1, 0, 0, 0, 953, 954, 1, 0, 0, 0, 954, 955, 3, 214, 107, 0, 955, 1181, 1, 0, 0, 0, 956, 958, 7, 7, 0, 0, 957, 959, 5, 258, 0, 0, 958, 957, 1, 0, 0, 0, 958, 959, 1, 0, 0, 0, 959, 961, 1, 0, 0, 0, 960, 962, 7, 8, 0, 0, 961, 960, 1, 0, 0, 0, 961, 962, 1, 0, 0, 0, 962, 963, 1, 0, 0, 0, 963, 965, 3, 214, 107, 0, 964, 966, 3, 42, 21, 0, 965, 964, 1, 0, 0, 0, 965, 966, 1, 0, 0, 0, 966, 968, 1, 0, 0, 0, 967, 969, 3, 52, 26, 0, 968, 967, 1, 0, 0, 0, 968, 969, 1, 0, 0, 0, 969, 1181, 1, 0, 0, 0, 970, 972, 7, 7, 0, 0, 971, 973, 5, 205, 0, 0, 972, 971, 1, 0, 0, 0, 972, 973, 1, 0, 0, 0, 973, 974, 1, 0, 0, 0, 974, 1181, 3, 36, 18, 0, 975, 976, 5, 45, 0, 0, 976, 977, 5, 177, 0, 0, 977, 978, 3, 46, 23, 0, 978, 979, 3, 214, 107, 0, 979, 980, 5, 133, 0, 0, 980, 981, 3, 340, 170, 0, 981, 1181, 1, 0, 0, 0, 982, 983, 5, 45, 0, 0, 983, 984, 5, 177, 0, 0, 984, 985, 5, 258, 0, 0, 985, 986, 3, 214, 107, 0, 986, 987, 5, 133, 0, 0, 987, 988, 3, 340, 170, 0, 988, 1181, 1, 0, 0, 0, 989, 990, 5, 212, 0, 0, 990, 991, 5, 258, 0, 0, 991, 1181, 3, 214, 107, 0, 992, 993, 5, 212, 0, 0, 993, 994, 5, 109, 0, 0, 994, 1181, 3, 214, 107, 0, 995, 1003, 5, 212, 0, 0, 996, 1004, 3, 338, 169, 0, 997, 999, 9, 0, 0, 0, 998, 997, 1, 0, 0, 0, 999, 1002, 1, 0, 0, 0, 1000, 1001, 1, 0, 0, 0, 1000, 998, 1, 0, 0, 0, 1001, 1004, 1, 0, 0, 0, 1002, 1000, 1, 0, 0, 0, 1003, 996, 1, 0, 0, 0, 1003, 1000, 1, 0, 0, 0, 1004, 1181, 1, 0, 0, 0, 1005, 1007, 5, 29, 0, 0, 1006, 1008, 5, 139, 0, 0, 1007, 1006, 1, 0, 0, 0, 1007, 1008, 1, 0, 0, 0, 1008, 1009, 1, 0, 0, 0, 1009, 1010, 5, 258, 0, 0, 1010, 1013, 3, 214, 107, 0, 1011, 1012, 5, 180, 0, 0, 1012, 1014, 3, 62, 31, 0, 1013, 1011, 1, 0, 0, 0, 1013, 1014, 1, 0, 0, 0, 1014, 1019, 1, 0, 0, 0, 1015, 1017, 5, 20, 0, 0, 1016, 1015, 1, 0, 0, 0, 1016, 1017, 1, 0, 0, 0, 1017, 1018, 1, 0, 0, 0, 1018, 1020, 3, 36, 18, 0, 1019, 1016, 1, 0, 0, 0, 1019, 1020, 1, 0, 0, 0, 1020, 1181, 1, 0, 0, 0, 1021, 1022, 5, 283, 0, 0, 1022, 1025, 5, 258, 0, 0, 1023, 1024, 5, 119, 0, 0, 1024, 1026, 5, 90, 0, 0, 1025, 1023, 1, 0, 0, 0, 1025, 1026, 1, 0, 0, 0, 1026, 1027, 1, 0, 0, 0, 1027, 1181, 3, 214, 107, 0, 1028, 1029, 5, 37, 0, 0, 1029, 1181, 5, 29, 0, 0, 1030, 1031, 5, 147, 0, 0, 1031, 1033, 5, 64, 0, 0, 1032, 1034, 5, 148, 0, 0, 1033, 1032, 1, 0, 0, 0, 1033, 1034, 1, 0, 0, 0, 1034, 1035, 1, 0, 0, 0, 1035, 1036, 5, 127, 0, 0, 1036, 1038, 3, 338, 169, 0, 1037, 1039, 5, 189, 0, 0, 1038, 1037, 1, 0, 0, 0, 1038, 1039, 1, 0, 0, 0, 1039, 1040, 1, 0, 0, 0, 1040, 1041, 5, 132, 0, 0, 1041, 1042, 5, 258, 0, 0, 1042, 1044, 3, 214, 107, 0, 1043, 1045, 3, 42, 21, 0, 1044, 1043, 1, 0, 0, 0, 1044, 1045, 1, 0, 0, 0, 1045, 1181, 1, 0, 0, 0, 1046, 1047, 5, 278, 0, 0, 1047, 1048, 5, 258, 0, 0, 1048, 1050, 3, 214, 107, 0, 1049, 1051, 3, 42, 21, 0, 1050, 1049, 1, 0, 0, 0, 1050, 1051, 1, 0, 0, 0, 1051, 1181, 1, 0, 0, 0, 1052, 1054, 5, 165, 0, 0, 1053, 1052, 1, 0, 0, 0, 1053, 1054, 1, 0, 0, 0, 1054, 1055, 1, 0, 0, 0, 1055, 1056, 5, 214, 0, 0, 1056, 1057, 5, 258, 0, 0, 1057, 1060, 3, 214, 107, 0, 1058, 1059, 7, 9, 0, 0, 1059, 1061, 5, 192, 0, 0, 1060, 1058, 1, 0, 0, 0, 1060, 1061, 1, 0, 0, 0, 1061, 1181, 1, 0, 0, 0, 1062, 1063, 7, 10, 0, 0, 1063, 1067, 3, 326, 163, 0, 1064, 1066, 9, 0, 0, 0, 1065, 1064, 1, 0, 0, 0, 1066, 1069, 1, 0, 0, 0, 1067, 1068, 1, 0, 0, 0, 1067, 1065, 1, 0, 0, 0, 1068, 1181, 1, 0, 0, 0, 1069, 1067, 1, 0, 0, 0, 1070, 1071, 5, 239, 0, 0, 1071, 1075, 5, 223, 0, 0, 1072, 1074, 9, 0, 0, 0, 1073, 1072, 1, 0, 0, 0, 1074, 1077, 1, 0, 0, 0, 1075, 1076, 1, 0, 0, 0, 1075, 1073, 1, 0, 0, 0, 1076, 1181, 1, 0, 0, 0, 1077, 1075, 1, 0, 0, 0, 1078, 1079, 5, 239, 0, 0, 1079, 1080, 5, 266, 0, 0, 1080, 1081, 5, 307, 0, 0, 1081, 1181, 3, 260, 130, 0, 1082, 1083, 5, 239, 0, 0, 1083, 1084, 5, 266, 0, 0, 1084, 1085, 5, 307, 0, 0, 1085, 1181, 3, 16, 8, 0, 1086, 1087, 5, 239, 0, 0, 1087, 1088, 5, 266, 0, 0, 1088, 1092, 5, 307, 0, 0, 1089, 1091, 9, 0, 0, 0, 1090, 1089, 1, 0, 0, 0, 1091, 1094, 1, 0, 0, 0, 1092, 1093, 1, 0, 0, 0, 1092, 1090, 1, 0, 0, 0, 1093, 1181, 1, 0, 0, 0, 1094, 1092, 1, 0, 0, 0, 1095, 1096, 5, 239, 0, 0, 1096, 1097, 3, 18, 9, 0, 1097, 1098, 5, 308, 0, 0, 1098, 1099, 3, 20, 10, 0, 1099, 1181, 1, 0, 0, 0, 1100, 1101, 5, 239, 0, 0, 1101, 1109, 3, 18, 9, 0, 1102, 1106, 5, 308, 0, 0, 1103, 1105, 9, 0, 0, 0, 1104, 1103, 1, 0, 0, 0, 1105, 1108, 1, 0, 0, 0, 1106, 1107, 1, 0, 0, 0, 1106, 1104, 1, 0, 0, 0, 1107, 1110, 1, 0, 0, 0, 1108, 1106, 1, 0, 0, 0, 1109, 1102, 1, 0, 0, 0, 1109, 1110, 1, 0, 0, 0, 1110, 1181, 1, 0, 0, 0, 1111, 1115, 5, 239, 0, 0, 1112, 1114, 9, 0, 0, 0, 1113, 1112, 1, 0, 0, 0, 1114, 1117, 1, 0, 0, 0, 1115, 1116, 1, 0, 0, 0, 1115, 1113, 1, 0, 0, 0, 1116, 1118, 1, 0, 0, 0, 1117, 1115, 1, 0, 0, 0, 1118, 1119, 5, 308, 0, 0, 1119, 1181, 3, 20, 10, 0, 1120, 1124, 5, 239, 0, 0, 1121, 1123, 9, 0, 0, 0, 1122, 1121, 1, 0, 0, 0, 1123, 1126, 1, 0, 0, 0, 1124, 1125, 1, 0, 0, 0, 1124, 1122, 1, 0, 0, 0, 1125, 1181, 1, 0, 0, 0, 1126, 1124, 1, 0, 0, 0, 1127, 1128, 5, 217, 0, 0, 1128, 1181, 3, 18, 9, 0, 1129, 1133, 5, 217, 0, 0, 1130, 1132, 9, 0, 0, 0, 1131, 1130, 1, 0, 0, 0, 1132, 1135, 1, 0, 0, 0, 1133, 1134, 1, 0, 0, 0, 1133, 1131, 1, 0, 0, 0, 1134, 1181, 1, 0, 0, 0, 1135, 1133, 1, 0, 0, 0, 1136, 1137, 5, 53, 0, 0, 1137, 1141, 5, 124, 0, 0, 1138, 1139, 5, 119, 0, 0, 1139, 1140, 5, 172, 0, 0, 1140, 1142, 5, 90, 0, 0, 1141, 1138, 1, 0, 0, 0, 1141, 1142, 1, 0, 0, 0, 1142, 1143, 1, 0, 0, 0, 1143, 1144, 3, 326, 163, 0, 1144, 1146, 5, 177, 0, 0, 1145, 1147, 5, 258, 0, 0, 1146, 1145, 1, 0, 0, 0, 1146, 1147, 1, 0, 0, 0, 1147, 1148, 1, 0, 0, 0, 1148, 1151, 3, 214, 107, 0, 1149, 1150, 5, 293, 0, 0, 1150, 1152, 3, 326, 163, 0, 1151, 1149, 1, 0, 0, 0, 1151, 1152, 1, 0, 0, 0, 1152, 1153, 1, 0, 0, 0, 1153, 1154, 5, 2, 0, 0, 1154, 1155, 3, 216, 108, 0, 1155, 1158, 5, 3, 0, 0, 1156, 1157, 5, 180, 0, 0, 1157, 1159, 3, 62, 31, 0, 1158, 1156, 1, 0, 0, 0, 1158, 1159, 1, 0, 0, 0, 1159, 1181, 1, 0, 0, 0, 1160, 1161, 5, 82, 0, 0, 1161, 1164, 5, 124, 0, 0, 1162, 1163, 5, 119, 0, 0, 1163, 1165, 5, 90, 0, 0, 1164, 1162, 1, 0, 0, 0, 1164, 1165, 1, 0, 0, 0, 1165, 1166, 1, 0, 0, 0, 1166, 1167, 3, 326, 163, 0, 1167, 1169, 5, 177, 0, 0, 1168, 1170, 5, 258, 0, 0, 1169, 1168, 1, 0, 0, 0, 1169, 1170, 1, 0, 0, 0, 1170, 1171, 1, 0, 0, 0, 1171, 1172, 3, 214, 107, 0, 1172, 1181, 1, 0, 0, 0, 1173, 1177, 3, 22, 11, 0, 1174, 1176, 9, 0, 0, 0, 1175, 1174, 1, 0, 0, 0, 1176, 1179, 1, 0, 0, 0, 1177, 1178, 1, 0, 0, 0, 1177, 1175, 1, 0, 0, 0, 1178, 1181, 1, 0, 0, 0, 1179, 1177, 1, 0, 0, 0, 1180, 377, 1, 0, 0, 0, 1180, 379, 1, 0, 0, 0, 1180, 382, 1, 0, 0, 0, 1180, 384, 1, 0, 0, 0, 1180, 388, 1, 0, 0, 0, 1180, 394, 1, 0, 0, 0, 1180, 412, 1, 0, 0, 0, 1180, 419, 1, 0, 0, 0, 1180, 425, 1, 0, 0, 0, 1180, 435, 1, 0, 0, 0, 1180, 447, 1, 0, 0, 0, 1180, 464, 1, 0, 0, 0, 1180, 485, 1, 0, 0, 0, 1180, 502, 1, 0, 0, 0, 1180, 519, 1, 0, 0, 0, 1180, 530, 1, 0, 0, 0, 1180, 537, 1, 0, 0, 0, 1180, 546, 1, 0, 0, 0, 1180, 555, 1, 0, 0, 0, 1180, 568, 1, 0, 0, 0, 1180, 579, 1, 0, 0, 0, 1180, 586, 1, 0, 0, 0, 1180, 593, 1, 0, 0, 0, 1180, 604, 1, 0, 0, 0, 1180, 615, 1, 0, 0, 0, 1180, 630, 1, 0, 0, 0, 1180, 642, 1, 0, 0, 0, 1180, 656, 1, 0, 0, 0, 1180, 666, 1, 0, 0, 0, 1180, 680, 1, 0, 0, 0, 1180, 688, 1, 0, 0, 0, 1180, 707, 1, 0, 0, 0, 1180, 716, 1, 0, 0, 0, 1180, 722, 1, 0, 0, 0, 1180, 732, 1, 0, 0, 0, 1180, 739, 1, 0, 0, 0, 1180, 774, 1, 0, 0, 0, 1180, 796, 1, 0, 0, 0, 1180, 804, 1, 0, 0, 0, 1180, 832, 1, 0, 0, 0, 1180, 842, 1, 0, 0, 0, 1180, 847, 1, 0, 0, 0, 1180, 859, 1, 0, 0, 0, 1180, 871, 1, 0, 0, 0, 1180, 880, 1, 0, 0, 0, 1180, 888, 1, 0, 0, 0, 1180, 900, 1, 0, 0, 0, 1180, 906, 1, 0, 0, 0, 1180, 924, 1, 0, 0, 0, 1180, 932, 1, 0, 0, 0, 1180, 935, 1, 0, 0, 0, 1180, 943, 1, 0, 0, 0, 1180, 949, 1, 0, 0, 0, 1180, 956, 1, 0, 0, 0, 1180, 970, 1, 0, 0, 0, 1180, 975, 1, 0, 0, 0, 1180, 982, 1, 0, 0, 0, 1180, 989, 1, 0, 0, 0, 1180, 992, 1, 0, 0, 0, 1180, 995, 1, 0, 0, 0, 1180, 1005, 1, 0, 0, 0, 1180, 1021, 1, 0, 0, 0, 1180, 1028, 1, 0, 0, 0, 1180, 1030, 1, 0, 0, 0, 1180, 1046, 1, 0, 0, 0, 1180, 1053, 1, 0, 0, 0, 1180, 1062, 1, 0, 0, 0, 1180, 1070, 1, 0, 0, 0, 1180, 1078, 1, 0, 0, 0, 1180, 1082, 1, 0, 0, 0, 1180, 1086, 1, 0, 0, 0, 1180, 1095, 1, 0, 0, 0, 1180, 1100, 1, 0, 0, 0, 1180, 1111, 1, 0, 0, 0, 1180, 1120, 1, 0, 0, 0, 1180, 1127, 1, 0, 0, 0, 1180, 1129, 1, 0, 0, 0, 1180, 1136, 1, 0, 0, 0, 1180, 1160, 1, 0, 0, 0, 1180, 1173, 1, 0, 0, 0, 1181, 15, 1, 0, 0, 0, 1182, 1185, 3, 338, 169, 0, 1183, 1185, 5, 148, 0, 0, 1184, 1182, 1, 0, 0, 0, 1184, 1183, 1, 0, 0, 0, 1185, 17, 1, 0, 0, 0, 1186, 1187, 3, 330, 165, 0, 1187, 19, 1, 0, 0, 0, 1188, 1189, 3, 332, 166, 0, 1189, 21, 1, 0, 0, 0, 1190, 1191, 5, 53, 0, 0, 1191, 1359, 5, 223, 0, 0, 1192, 1193, 5, 82, 0, 0, 1193, 1359, 5, 223, 0, 0, 1194, 1196, 5, 113, 0, 0, 1195, 1197, 5, 223, 0, 0, 1196, 1195, 1, 0, 0, 0, 1196, 1197, 1, 0, 0, 0, 1197, 1359, 1, 0, 0, 0, 1198, 1200, 5, 220, 0, 0, 1199, 1201, 5, 223, 0, 0, 1200, 1199, 1, 0, 0, 0, 1200, 1201, 1, 0, 0, 0, 1201, 1359, 1, 0, 0, 0, 1202, 1203, 5, 242, 0, 0, 1203, 1359, 5, 113, 0, 0, 1204, 1205, 5, 242, 0, 0, 1205, 1207, 5, 223, 0, 0, 1206, 1208, 5, 113, 0, 0, 1207, 1206, 1, 0, 0, 0, 1207, 1208, 1, 0, 0, 0, 1208, 1359, 1, 0, 0, 0, 1209, 1210, 5, 242, 0, 0, 1210, 1359, 5, 201, 0, 0, 1211, 1212, 5, 242, 0, 0, 1212, 1359, 5, 224, 0, 0, 1213, 1214, 5, 242, 0, 0, 1214, 1215, 5, 56, 0, 0, 1215, 1359, 5, 224, 0, 0, 1216, 1217, 5, 92, 0, 0, 1217, 1359, 5, 258, 0, 0, 1218, 1219, 5, 121, 0, 0, 1219, 1359, 5, 258, 0, 0, 1220, 1221, 5, 242, 0, 0, 1221, 1359, 5, 48, 0, 0, 1222, 1223, 5, 242, 0, 0, 1223, 1224, 5, 53, 0, 0, 1224, 1359, 5, 258, 0, 0, 1225, 1226, 5, 242, 0, 0, 1226, 1359, 5, 274, 0, 0, 1227, 1228, 5, 242, 0, 0, 1228, 1359, 5, 125, 0, 0, 1229, 1230, 5, 242, 0, 0, 1230, 1359, 5, 151, 0, 0, 1231, 1232, 5, 53, 0, 0, 1232, 1359, 5, 124, 0, 0, 1233, 1234, 5, 82, 0, 0, 1234, 1359, 5, 124, 0, 0, 1235, 1236, 5, 11, 0, 0, 1236, 1359, 5, 124, 0, 0, 1237, 1238, 5, 150, 0, 0, 1238, 1359, 5, 258, 0, 0, 1239, 1240, 5, 150, 0, 0, 1240, 1359, 5, 65, 0, 0, 1241, 1242, 5, 287, 0, 0, 1242, 1359, 5, 258, 0, 0, 1243, 1244, 5, 287, 0, 0, 1244, 1359, 5, 65, 0, 0, 1245, 1246, 5, 53, 0, 0, 1246, 1247, 5, 263, 0, 0, 1247, 1359, 5, 153, 0, 0, 1248, 1249, 5, 82, 0, 0, 1249, 1250, 5, 263, 0, 0, 1250, 1359, 5, 153, 0, 0, 1251, 1252, 5, 11, 0, 0, 1252, 1253, 5, 258, 0, 0, 1253, 1254, 3, 220, 110, 0, 1254, 1255, 5, 172, 0, 0, 1255, 1256, 5, 39, 0, 0, 1256, 1359, 1, 0, 0, 0, 1257, 1258, 5, 11, 0, 0, 1258, 1259, 5, 258, 0, 0, 1259, 1260, 3, 220, 110, 0, 1260, 1261, 5, 39, 0, 0, 1261, 1262, 5, 28, 0, 0, 1262, 1359, 1, 0, 0, 0, 1263, 1264, 5, 11, 0, 0, 1264, 1265, 5, 258, 0, 0, 1265, 1266, 3, 220, 110, 0, 1266, 1267, 5, 172, 0, 0, 1267, 1268, 5, 246, 0, 0, 1268, 1359, 1, 0, 0, 0, 1269, 1270, 5, 11, 0, 0, 1270, 1271, 5, 258, 0, 0, 1271, 1272, 3, 220, 110, 0, 1272, 1273, 5, 243, 0, 0, 1273, 1274, 5, 28, 0, 0, 1274, 1359, 1, 0, 0, 0, 1275, 1276, 5, 11, 0, 0, 1276, 1277, 5, 258, 0, 0, 1277, 1278, 3, 220, 110, 0, 1278, 1279, 5, 172, 0, 0, 1279, 1280, 5, 243, 0, 0, 1280, 1359, 1, 0, 0, 0, 1281, 1282, 5, 11, 0, 0, 1282, 1283, 5, 258, 0, 0, 1283, 1284, 3, 220, 110, 0, 1284, 1285, 5, 172, 0, 0, 1285, 1286, 5, 250, 0, 0, 1286, 1287, 5, 20, 0, 0, 1287, 1288, 5, 77, 0, 0, 1288, 1359, 1, 0, 0, 0, 1289, 1290, 5, 11, 0, 0, 1290, 1291, 5, 258, 0, 0, 1291, 1292, 3, 220, 110, 0, 1292, 1293, 5, 239, 0, 0, 1293, 1294, 5, 243, 0, 0, 1294, 1295, 5, 149, 0, 0, 1295, 1359, 1, 0, 0, 0, 1296, 1297, 5, 11, 0, 0, 1297, 1298, 5, 258, 0, 0, 1298, 1299, 3, 220, 110, 0, 1299, 1300, 5, 88, 0, 0, 1300, 1301, 5, 190, 0, 0, 1301, 1359, 1, 0, 0, 0, 1302, 1303, 5, 11, 0, 0, 1303, 1304, 5, 258, 0, 0, 1304, 1305, 3, 220, 110, 0, 1305, 1306, 5, 18, 0, 0, 1306, 1307, 5, 190, 0, 0, 1307, 1359, 1, 0, 0, 0, 1308, 1309, 5, 11, 0, 0, 1309, 1310, 5, 258, 0, 0, 1310, 1311, 3, 220, 110, 0, 1311, 1312, 5, 281, 0, 0, 1312, 1313, 5, 190, 0, 0, 1313, 1359, 1, 0, 0, 0, 1314, 1315, 5, 11, 0, 0, 1315, 1316, 5, 258, 0, 0, 1316, 1317, 3, 220, 110, 0, 1317, 1318, 5, 271, 0, 0, 1318, 1359, 1, 0, 0, 0, 1319, 1320, 5, 11, 0, 0, 1320, 1321, 5, 258, 0, 0, 1321, 1323, 3, 220, 110, 0, 1322, 1324, 3, 42, 21, 0, 1323, 1322, 1, 0, 0, 0, 1323, 1324, 1, 0, 0, 0, 1324, 1325, 1, 0, 0, 0, 1325, 1326, 5, 47, 0, 0, 1326, 1359, 1, 0, 0, 0, 1327, 1328, 5, 11, 0, 0, 1328, 1329, 5, 258, 0, 0, 1329, 1331, 3, 220, 110, 0, 1330, 1332, 3, 42, 21, 0, 1331, 1330, 1, 0, 0, 0, 1331, 1332, 1, 0, 0, 0, 1332, 1333, 1, 0, 0, 0, 1333, 1334, 5, 50, 0, 0, 1334, 1359, 1, 0, 0, 0, 1335, 1336, 5, 11, 0, 0, 1336, 1337, 5, 258, 0, 0, 1337, 1339, 3, 220, 110, 0, 1338, 1340, 3, 42, 21, 0, 1339, 1338, 1, 0, 0, 0, 1339, 1340, 1, 0, 0, 0, 1340, 1341, 1, 0, 0, 0, 1341, 1342, 5, 239, 0, 0, 1342, 1343, 5, 100, 0, 0, 1343, 1359, 1, 0, 0, 0, 1344, 1345, 5, 11, 0, 0, 1345, 1346, 5, 258, 0, 0, 1346, 1348, 3, 220, 110, 0, 1347, 1349, 3, 42, 21, 0, 1348, 1347, 1, 0, 0, 0, 1348, 1349, 1, 0, 0, 0, 1349, 1350, 1, 0, 0, 0, 1350, 1351, 5, 216, 0, 0, 1351, 1352, 5, 44, 0, 0, 1352, 1359, 1, 0, 0, 0, 1353, 1354, 5, 248, 0, 0, 1354, 1359, 5, 273, 0, 0, 1355, 1359, 5, 46, 0, 0, 1356, 1359, 5, 225, 0, 0, 1357, 1359, 5, 76, 0, 0, 1358, 1190, 1, 0, 0, 0, 1358, 1192, 1, 0, 0, 0, 1358, 1194, 1, 0, 0, 0, 1358, 1198, 1, 0, 0, 0, 1358, 1202, 1, 0, 0, 0, 1358, 1204, 1, 0, 0, 0, 1358, 1209, 1, 0, 0, 0, 1358, 1211, 1, 0, 0, 0, 1358, 1213, 1, 0, 0, 0, 1358, 1216, 1, 0, 0, 0, 1358, 1218, 1, 0, 0, 0, 1358, 1220, 1, 0, 0, 0, 1358, 1222, 1, 0, 0, 0, 1358, 1225, 1, 0, 0, 0, 1358, 1227, 1, 0, 0, 0, 1358, 1229, 1, 0, 0, 0, 1358, 1231, 1, 0, 0, 0, 1358, 1233, 1, 0, 0, 0, 1358, 1235, 1, 0, 0, 0, 1358, 1237, 1, 0, 0, 0, 1358, 1239, 1, 0, 0, 0, 1358, 1241, 1, 0, 0, 0, 1358, 1243, 1, 0, 0, 0, 1358, 1245, 1, 0, 0, 0, 1358, 1248, 1, 0, 0, 0, 1358, 1251, 1, 0, 0, 0, 1358, 1257, 1, 0, 0, 0, 1358, 1263, 1, 0, 0, 0, 1358, 1269, 1, 0, 0, 0, 1358, 1275, 1, 0, 0, 0, 1358, 1281, 1, 0, 0, 0, 1358, 1289, 1, 0, 0, 0, 1358, 1296, 1, 0, 0, 0, 1358, 1302, 1, 0, 0, 0, 1358, 1308, 1, 0, 0, 0, 1358, 1314, 1, 0, 0, 0, 1358, 1319, 1, 0, 0, 0, 1358, 1327, 1, 0, 0, 0, 1358, 1335, 1, 0, 0, 0, 1358, 1344, 1, 0, 0, 0, 1358, 1353, 1, 0, 0, 0, 1358, 1355, 1, 0, 0, 0, 1358, 1356, 1, 0, 0, 0, 1358, 1357, 1, 0, 0, 0, 1359, 23, 1, 0, 0, 0, 1360, 1362, 5, 53, 0, 0, 1361, 1363, 5, 263, 0, 0, 1362, 1361, 1, 0, 0, 0, 1362, 1363, 1, 0, 0, 0, 1363, 1365, 1, 0, 0, 0, 1364, 1366, 5, 94, 0, 0, 1365, 1364, 1, 0, 0, 0, 1365, 1366, 1, 0, 0, 0, 1366, 1367, 1, 0, 0, 0, 1367, 1371, 5, 258, 0, 0, 1368, 1369, 5, 119, 0, 0, 1369, 1370, 5, 172, 0, 0, 1370, 1372, 5, 90, 0, 0, 1371, 1368, 1, 0, 0, 0, 1371, 1372, 1, 0, 0, 0, 1372, 1373, 1, 0, 0, 0, 1373, 1374, 3, 214, 107, 0, 1374, 25, 1, 0, 0, 0, 1375, 1376, 5, 53, 0, 0, 1376, 1378, 5, 181, 0, 0, 1377, 1375, 1, 0, 0, 0, 1377, 1378, 1, 0, 0, 0, 1378, 1379, 1, 0, 0, 0, 1379, 1380, 5, 216, 0, 0, 1380, 1381, 5, 258, 0, 0, 1381, 1382, 3, 214, 107, 0, 1382, 27, 1, 0, 0, 0, 1383, 1384, 5, 39, 0, 0, 1384, 1385, 5, 28, 0, 0, 1385, 1389, 3, 190, 95, 0, 1386, 1387, 5, 246, 0, 0, 1387, 1388, 5, 28, 0, 0, 1388, 1390, 3, 194, 97, 0, 1389, 1386, 1, 0, 0, 0, 1389, 1390, 1, 0, 0, 0, 1390, 1391, 1, 0, 0, 0, 1391, 1392, 5, 132, 0, 0, 1392, 1393, 5, 335, 0, 0, 1393, 1394, 5, 27, 0, 0, 1394, 29, 1, 0, 0, 0, 1395, 1396, 5, 243, 0, 0, 1396, 1397, 5, 28, 0, 0, 1397, 1398, 3, 190, 95, 0, 1398, 1401, 5, 177, 0, 0, 1399, 1402, 3, 70, 35, 0, 1400, 1402, 3, 72, 36, 0, 1401, 1399, 1, 0, 0, 0, 1401, 1400, 1, 0, 0, 0, 1402, 1406, 1, 0, 0, 0, 1403, 1404, 5, 250, 0, 0, 1404, 1405, 5, 20, 0, 0, 1405, 1407, 5, 77, 0, 0, 1406, 1403, 1, 0, 0, 0, 1406, 1407, 1, 0, 0, 0, 1407, 31, 1, 0, 0, 0, 1408, 1409, 5, 149, 0, 0, 1409, 1410, 3, 338, 169, 0, 1410, 33, 1, 0, 0, 0, 1411, 1412, 5, 45, 0, 0, 1412, 1413, 3, 338, 169, 0, 1413, 35, 1, 0, 0, 0, 1414, 1416, 3, 54, 27, 0, 1415, 1414, 1, 0, 0, 0, 1415, 1416, 1, 0, 0, 0, 1416, 1417, 1, 0, 0, 0, 1417, 1418, 3, 88, 44, 0, 1418, 1419, 3, 84, 42, 0, 1419, 37, 1, 0, 0, 0, 1420, 1421, 5, 129, 0, 0, 1421, 1423, 5, 189, 0, 0, 1422, 1424, 5, 258, 0, 0, 1423, 1422, 1, 0, 0, 0, 1423, 1424, 1, 0, 0, 0, 1424, 1425, 1, 0, 0, 0, 1425, 1432, 3, 214, 107, 0, 1426, 1430, 3, 42, 21, 0, 1427, 1428, 5, 119, 0, 0, 1428, 1429, 5, 172, 0, 0, 1429, 1431, 5, 90, 0, 0, 1430, 1427, 1, 0, 0, 0, 1430, 1431, 1, 0, 0, 0, 1431, 1433, 1, 0, 0, 0, 1432, 1426, 1, 0, 0, 0, 1432, 1433, 1, 0, 0, 0, 1433, 1435, 1, 0, 0, 0, 1434, 1436, 3, 190, 95, 0, 1435, 1434, 1, 0, 0, 0, 1435, 1436, 1, 0, 0, 0, 1436, 1491, 1, 0, 0, 0, 1437, 1438, 5, 129, 0, 0, 1438, 1440, 5, 132, 0, 0, 1439, 1441, 5, 258, 0, 0, 1440, 1439, 1, 0, 0, 0, 1440, 1441, 1, 0, 0, 0, 1441, 1442, 1, 0, 0, 0, 1442, 1444, 3, 214, 107, 0, 1443, 1445, 3, 42, 21, 0, 1444, 1443, 1, 0, 0, 0, 1444, 1445, 1, 0, 0, 0, 1445, 1449, 1, 0, 0, 0, 1446, 1447, 5, 119, 0, 0, 1447, 1448, 5, 172, 0, 0, 1448, 1450, 5, 90, 0, 0, 1449, 1446, 1, 0, 0, 0, 1449, 1450, 1, 0, 0, 0, 1450, 1452, 1, 0, 0, 0, 1451, 1453, 3, 190, 95, 0, 1452, 1451, 1, 0, 0, 0, 1452, 1453, 1, 0, 0, 0, 1453, 1491, 1, 0, 0, 0, 1454, 1455, 5, 129, 0, 0, 1455, 1457, 5, 132, 0, 0, 1456, 1458, 5, 258, 0, 0, 1457, 1456, 1, 0, 0, 0, 1457, 1458, 1, 0, 0, 0, 1458, 1459, 1, 0, 0, 0, 1459, 1460, 3, 214, 107, 0, 1460, 1461, 5, 216, 0, 0, 1461, 1462, 3, 122, 61, 0, 1462, 1491, 1, 0, 0, 0, 1463, 1464, 5, 129, 0, 0, 1464, 1466, 5, 189, 0, 0, 1465, 1467, 5, 148, 0, 0, 1466, 1465, 1, 0, 0, 0, 1466, 1467, 1, 0, 0, 0, 1467, 1468, 1, 0, 0, 0, 1468, 1469, 5, 78, 0, 0, 1469, 1471, 3, 338, 169, 0, 1470, 1472, 3, 210, 105, 0, 1471, 1470, 1, 0, 0, 0, 1471, 1472, 1, 0, 0, 0, 1472, 1474, 1, 0, 0, 0, 1473, 1475, 3, 74, 37, 0, 1474, 1473, 1, 0, 0, 0, 1474, 1475, 1, 0, 0, 0, 1475, 1491, 1, 0, 0, 0, 1476, 1477, 5, 129, 0, 0, 1477, 1479, 5, 189, 0, 0, 1478, 1480, 5, 148, 0, 0, 1479, 1478, 1, 0, 0, 0, 1479, 1480, 1, 0, 0, 0, 1480, 1481, 1, 0, 0, 0, 1481, 1483, 5, 78, 0, 0, 1482, 1484, 3, 338, 169, 0, 1483, 1482, 1, 0, 0, 0, 1483, 1484, 1, 0, 0, 0, 1484, 1485, 1, 0, 0, 0, 1485, 1488, 3, 58, 29, 0, 1486, 1487, 5, 180, 0, 0, 1487, 1489, 3, 62, 31, 0, 1488, 1486, 1, 0, 0, 0, 1488, 1489, 1, 0, 0, 0, 1489, 1491, 1, 0, 0, 0, 1490, 1420, 1, 0, 0, 0, 1490, 1437, 1, 0, 0, 0, 1490, 1454, 1, 0, 0, 0, 1490, 1463, 1, 0, 0, 0, 1490, 1476, 1, 0, 0, 0, 1491, 39, 1, 0, 0, 0, 1492, 1494, 3, 42, 21, 0, 1493, 1495, 3, 32, 16, 0, 1494, 1493, 1, 0, 0, 0, 1494, 1495, 1, 0, 0, 0, 1495, 41, 1, 0, 0, 0, 1496, 1497, 5, 190, 0, 0, 1497, 1498, 5, 2, 0, 0, 1498, 1503, 3, 44, 22, 0, 1499, 1500, 5, 4, 0, 0, 1500, 1502, 3, 44, 22, 0, 1501, 1499, 1, 0, 0, 0, 1502, 1505, 1, 0, 0, 0, 1503, 1501, 1, 0, 0, 0, 1503, 1504, 1, 0, 0, 0, 1504, 1506, 1, 0, 0, 0, 1505, 1503, 1, 0, 0, 0, 1506, 1507, 5, 3, 0, 0, 1507, 43, 1, 0, 0, 0, 1508, 1511, 3, 326, 163, 0, 1509, 1510, 5, 308, 0, 0, 1510, 1512, 3, 250, 125, 0, 1511, 1509, 1, 0, 0, 0, 1511, 1512, 1, 0, 0, 0, 1512, 1518, 1, 0, 0, 0, 1513, 1514, 3, 326, 163, 0, 1514, 1515, 5, 308, 0, 0, 1515, 1516, 5, 70, 0, 0, 1516, 1518, 1, 0, 0, 0, 1517, 1508, 1, 0, 0, 0, 1517, 1513, 1, 0, 0, 0, 1518, 45, 1, 0, 0, 0, 1519, 1520, 7, 11, 0, 0, 1520, 47, 1, 0, 0, 0, 1521, 1522, 7, 12, 0, 0, 1522, 49, 1, 0, 0, 0, 1523, 1529, 3, 320, 160, 0, 1524, 1529, 3, 338, 169, 0, 1525, 1529, 3, 252, 126, 0, 1526, 1529, 3, 254, 127, 0, 1527, 1529, 3, 256, 128, 0, 1528, 1523, 1, 0, 0, 0, 1528, 1524, 1, 0, 0, 0, 1528, 1525, 1, 0, 0, 0, 1528, 1526, 1, 0, 0, 0, 1528, 1527, 1, 0, 0, 0, 1529, 51, 1, 0, 0, 0, 1530, 1535, 3, 326, 163, 0, 1531, 1532, 5, 5, 0, 0, 1532, 1534, 3, 326, 163, 0, 1533, 1531, 1, 0, 0, 0, 1534, 1537, 1, 0, 0, 0, 1535, 1533, 1, 0, 0, 0, 1535, 1536, 1, 0, 0, 0, 1536, 53, 1, 0, 0, 0, 1537, 1535, 1, 0, 0, 0, 1538, 1539, 5, 303, 0, 0, 1539, 1544, 3, 56, 28, 0, 1540, 1541, 5, 4, 0, 0, 1541, 1543, 3, 56, 28, 0, 1542, 1540, 1, 0, 0, 0, 1543, 1546, 1, 0, 0, 0, 1544, 1542, 1, 0, 0, 0, 1544, 1545, 1, 0, 0, 0, 1545, 55, 1, 0, 0, 0, 1546, 1544, 1, 0, 0, 0, 1547, 1549, 3, 322, 161, 0, 1548, 1550, 3, 190, 95, 0, 1549, 1548, 1, 0, 0, 0, 1549, 1550, 1, 0, 0, 0, 1550, 1552, 1, 0, 0, 0, 1551, 1553, 5, 20, 0, 0, 1552, 1551, 1, 0, 0, 0, 1552, 1553, 1, 0, 0, 0, 1553, 1554, 1, 0, 0, 0, 1554, 1555, 5, 2, 0, 0, 1555, 1556, 3, 36, 18, 0, 1556, 1557, 5, 3, 0, 0, 1557, 57, 1, 0, 0, 0, 1558, 1559, 5, 293, 0, 0, 1559, 1560, 3, 214, 107, 0, 1560, 59, 1, 0, 0, 0, 1561, 1562, 5, 180, 0, 0, 1562, 1575, 3, 62, 31, 0, 1563, 1564, 5, 191, 0, 0, 1564, 1565, 5, 28, 0, 0, 1565, 1575, 3, 228, 114, 0, 1566, 1575, 3, 30, 15, 0, 1567, 1575, 3, 28, 14, 0, 1568, 1575, 3, 210, 105, 0, 1569, 1575, 3, 74, 37, 0, 1570, 1575, 3, 32, 16, 0, 1571, 1575, 3, 34, 17, 0, 1572, 1573, 5, 262, 0, 0, 1573, 1575, 3, 62, 31, 0, 1574, 1561, 1, 0, 0, 0, 1574, 1563, 1, 0, 0, 0, 1574, 1566, 1, 0, 0, 0, 1574, 1567, 1, 0, 0, 0, 1574, 1568, 1, 0, 0, 0, 1574, 1569, 1, 0, 0, 0, 1574, 1570, 1, 0, 0, 0, 1574, 1571, 1, 0, 0, 0, 1574, 1572, 1, 0, 0, 0, 1575, 1578, 1, 0, 0, 0, 1576, 1574, 1, 0, 0, 0, 1576, 1577, 1, 0, 0, 0, 1577, 61, 1, 0, 0, 0, 1578, 1576, 1, 0, 0, 0, 1579, 1580, 5, 2, 0, 0, 1580, 1585, 3, 64, 32, 0, 1581, 1582, 5, 4, 0, 0, 1582, 1584, 3, 64, 32, 0, 1583, 1581, 1, 0, 0, 0, 1584, 1587, 1, 0, 0, 0, 1585, 1583, 1, 0, 0, 0, 1585, 1586, 1, 0, 0, 0, 1586, 1588, 1, 0, 0, 0, 1587, 1585, 1, 0, 0, 0, 1588, 1589, 5, 3, 0, 0, 1589, 63, 1, 0, 0, 0, 1590, 1595, 3, 66, 33, 0, 1591, 1593, 5, 308, 0, 0, 1592, 1591, 1, 0, 0, 0, 1592, 1593, 1, 0, 0, 0, 1593, 1594, 1, 0, 0, 0, 1594, 1596, 3, 68, 34, 0, 1595, 1592, 1, 0, 0, 0, 1595, 1596, 1, 0, 0, 0, 1596, 65, 1, 0, 0, 0, 1597, 1602, 3, 326, 163, 0, 1598, 1599, 5, 5, 0, 0, 1599, 1601, 3, 326, 163, 0, 1600, 1598, 1, 0, 0, 0, 1601, 1604, 1, 0, 0, 0, 1602, 1600, 1, 0, 0, 0, 1602, 1603, 1, 0, 0, 0, 1603, 1607, 1, 0, 0, 0, 1604, 1602, 1, 0, 0, 0, 1605, 1607, 3, 338, 169, 0, 1606, 1597, 1, 0, 0, 0, 1606, 1605, 1, 0, 0, 0, 1607, 67, 1, 0, 0, 0, 1608, 1613, 5, 335, 0, 0, 1609, 1613, 5, 337, 0, 0, 1610, 1613, 3, 258, 129, 0, 1611, 1613, 3, 338, 169, 0, 1612, 1608, 1, 0, 0, 0, 1612, 1609, 1, 0, 0, 0, 1612, 1610, 1, 0, 0, 0, 1612, 1611, 1, 0, 0, 0, 1613, 69, 1, 0, 0, 0, 1614, 1615, 5, 2, 0, 0, 1615, 1620, 3, 250, 125, 0, 1616, 1617, 5, 4, 0, 0, 1617, 1619, 3, 250, 125, 0, 1618, 1616, 1, 0, 0, 0, 1619, 1622, 1, 0, 0, 0, 1620, 1618, 1, 0, 0, 0, 1620, 1621, 1, 0, 0, 0, 1621, 1623, 1, 0, 0, 0, 1622, 1620, 1, 0, 0, 0, 1623, 1624, 5, 3, 0, 0, 1624, 71, 1, 0, 0, 0, 1625, 1626, 5, 2, 0, 0, 1626, 1631, 3, 70, 35, 0, 1627, 1628, 5, 4, 0, 0, 1628, 1630, 3, 70, 35, 0, 1629, 1627, 1, 0, 0, 0, 1630, 1633, 1, 0, 0, 0, 1631, 1629, 1, 0, 0, 0, 1631, 1632, 1, 0, 0, 0, 1632, 1634, 1, 0, 0, 0, 1633, 1631, 1, 0, 0, 0, 1634, 1635, 5, 3, 0, 0, 1635, 73, 1, 0, 0, 0, 1636, 1637, 5, 250, 0, 0, 1637, 1638, 5, 20, 0, 0, 1638, 1643, 3, 76, 38, 0, 1639, 1640, 5, 250, 0, 0, 1640, 1641, 5, 28, 0, 0, 1641, 1643, 3, 78, 39, 0, 1642, 1636, 1, 0, 0, 0, 1642, 1639, 1, 0, 0, 0, 1643, 75, 1, 0, 0, 0, 1644, 1645, 5, 128, 0, 0, 1645, 1646, 3, 338, 169, 0, 1646, 1647, 5, 185, 0, 0, 1647, 1648, 3, 338, 169, 0, 1648, 1651, 1, 0, 0, 0, 1649, 1651, 3, 326, 163, 0, 1650, 1644, 1, 0, 0, 0, 1650, 1649, 1, 0, 0, 0, 1651, 77, 1, 0, 0, 0, 1652, 1656, 3, 338, 169, 0, 1653, 1654, 5, 303, 0, 0, 1654, 1655, 5, 237, 0, 0, 1655, 1657, 3, 62, 31, 0, 1656, 1653, 1, 0, 0, 0, 1656, 1657, 1, 0, 0, 0, 1657, 79, 1, 0, 0, 0, 1658, 1659, 3, 326, 163, 0, 1659, 1660, 3, 338, 169, 0, 1660, 81, 1, 0, 0, 0, 1661, 1662, 3, 38, 19, 0, 1662, 1663, 3, 36, 18, 0, 1663, 1718, 1, 0, 0, 0, 1664, 1666, 3, 130, 65, 0, 1665, 1667, 3, 86, 43, 0, 1666, 1665, 1, 0, 0, 0, 1667, 1668, 1, 0, 0, 0, 1668, 1666, 1, 0, 0, 0, 1668, 1669, 1, 0, 0, 0, 1669, 1718, 1, 0, 0, 0, 1670, 1671, 5, 72, 0, 0, 1671, 1672, 5, 107, 0, 0, 1672, 1673, 3, 214, 107, 0, 1673, 1675, 3, 208, 104, 0, 1674, 1676, 3, 122, 61, 0, 1675, 1674, 1, 0, 0, 0, 1675, 1676, 1, 0, 0, 0, 1676, 1718, 1, 0, 0, 0, 1677, 1678, 5, 290, 0, 0, 1678, 1679, 3, 214, 107, 0, 1679, 1680, 3, 208, 104, 0, 1680, 1682, 3, 104, 52, 0, 1681, 1683, 3, 122, 61, 0, 1682, 1681, 1, 0, 0, 0, 1682, 1683, 1, 0, 0, 0, 1683, 1718, 1, 0, 0, 0, 1684, 1685, 5, 156, 0, 0, 1685, 1686, 5, 132, 0, 0, 1686, 1687, 3, 214, 107, 0, 1687, 1688, 3, 208, 104, 0, 1688, 1694, 5, 293, 0, 0, 1689, 1695, 3, 214, 107, 0, 1690, 1691, 5, 2, 0, 0, 1691, 1692, 3, 36, 18, 0, 1692, 1693, 5, 3, 0, 0, 1693, 1695, 1, 0, 0, 0, 1694, 1689, 1, 0, 0, 0, 1694, 1690, 1, 0, 0, 0, 1695, 1696, 1, 0, 0, 0, 1696, 1697, 3, 208, 104, 0, 1697, 1698, 5, 177, 0, 0, 1698, 1702, 3, 240, 120, 0, 1699, 1701, 3, 106, 53, 0, 1700, 1699, 1, 0, 0, 0, 1701, 1704, 1, 0, 0, 0, 1702, 1700, 1, 0, 0, 0, 1702, 1703, 1, 0, 0, 0, 1703, 1708, 1, 0, 0, 0, 1704, 1702, 1, 0, 0, 0, 1705, 1707, 3, 108, 54, 0, 1706, 1705, 1, 0, 0, 0, 1707, 1710, 1, 0, 0, 0, 1708, 1706, 1, 0, 0, 0, 1708, 1709, 1, 0, 0, 0, 1709, 1714, 1, 0, 0, 0, 1710, 1708, 1, 0, 0, 0, 1711, 1713, 3, 110, 55, 0, 1712, 1711, 1, 0, 0, 0, 1713, 1716, 1, 0, 0, 0, 1714, 1712, 1, 0, 0, 0, 1714, 1715, 1, 0, 0, 0, 1715, 1718, 1, 0, 0, 0, 1716, 1714, 1, 0, 0, 0, 1717, 1661, 1, 0, 0, 0, 1717, 1664, 1, 0, 0, 0, 1717, 1670, 1, 0, 0, 0, 1717, 1677, 1, 0, 0, 0, 1717, 1684, 1, 0, 0, 0, 1718, 83, 1, 0, 0, 0, 1719, 1720, 5, 182, 0, 0, 1720, 1721, 5, 28, 0, 0, 1721, 1726, 3, 92, 46, 0, 1722, 1723, 5, 4, 0, 0, 1723, 1725, 3, 92, 46, 0, 1724, 1722, 1, 0, 0, 0, 1725, 1728, 1, 0, 0, 0, 1726, 1724, 1, 0, 0, 0, 1726, 1727, 1, 0, 0, 0, 1727, 1730, 1, 0, 0, 0, 1728, 1726, 1, 0, 0, 0, 1729, 1719, 1, 0, 0, 0, 1729, 1730, 1, 0, 0, 0, 1730, 1741, 1, 0, 0, 0, 1731, 1732, 5, 38, 0, 0, 1732, 1733, 5, 28, 0, 0, 1733, 1738, 3, 236, 118, 0, 1734, 1735, 5, 4, 0, 0, 1735, 1737, 3, 236, 118, 0, 1736, 1734, 1, 0, 0, 0, 1737, 1740, 1, 0, 0, 0, 1738, 1736, 1, 0, 0, 0, 1738, 1739, 1, 0, 0, 0, 1739, 1742, 1, 0, 0, 0, 1740, 1738, 1, 0, 0, 0, 1741, 1731, 1, 0, 0, 0, 1741, 1742, 1, 0, 0, 0, 1742, 1753, 1, 0, 0, 0, 1743, 1744, 5, 80, 0, 0, 1744, 1745, 5, 28, 0, 0, 1745, 1750, 3, 236, 118, 0, 1746, 1747, 5, 4, 0, 0, 1747, 1749, 3, 236, 118, 0, 1748, 1746, 1, 0, 0, 0, 1749, 1752, 1, 0, 0, 0, 1750, 1748, 1, 0, 0, 0, 1750, 1751, 1, 0, 0, 0, 1751, 1754, 1, 0, 0, 0, 1752, 1750, 1, 0, 0, 0, 1753, 1743, 1, 0, 0, 0, 1753, 1754, 1, 0, 0, 0, 1754, 1765, 1, 0, 0, 0, 1755, 1756, 5, 245, 0, 0, 1756, 1757, 5, 28, 0, 0, 1757, 1762, 3, 92, 46, 0, 1758, 1759, 5, 4, 0, 0, 1759, 1761, 3, 92, 46, 0, 1760, 1758, 1, 0, 0, 0, 1761, 1764, 1, 0, 0, 0, 1762, 1760, 1, 0, 0, 0, 1762, 1763, 1, 0, 0, 0, 1763, 1766, 1, 0, 0, 0, 1764, 1762, 1, 0, 0, 0, 1765, 1755, 1, 0, 0, 0, 1765, 1766, 1, 0, 0, 0, 1766, 1768, 1, 0, 0, 0, 1767, 1769, 3, 306, 153, 0, 1768, 1767, 1, 0, 0, 0, 1768, 1769, 1, 0, 0, 0, 1769, 1775, 1, 0, 0, 0, 1770, 1773, 5, 144, 0, 0, 1771, 1774, 5, 10, 0, 0, 1772, 1774, 3, 236, 118, 0, 1773, 1771, 1, 0, 0, 0, 1773, 1772, 1, 0, 0, 0, 1774, 1776, 1, 0, 0, 0, 1775, 1770, 1, 0, 0, 0, 1775, 1776, 1, 0, 0, 0, 1776, 1779, 1, 0, 0, 0, 1777, 1778, 5, 176, 0, 0, 1778, 1780, 3, 236, 118, 0, 1779, 1777, 1, 0, 0, 0, 1779, 1780, 1, 0, 0, 0, 1780, 85, 1, 0, 0, 0, 1781, 1782, 3, 38, 19, 0, 1782, 1783, 3, 96, 48, 0, 1783, 87, 1, 0, 0, 0, 1784, 1785, 6, 44, -1, 0, 1785, 1786, 3, 90, 45, 0, 1786, 1795, 1, 0, 0, 0, 1787, 1788, 10, 1, 0, 0, 1788, 1790, 7, 13, 0, 0, 1789, 1791, 3, 174, 87, 0, 1790, 1789, 1, 0, 0, 0, 1790, 1791, 1, 0, 0, 0, 1791, 1792, 1, 0, 0, 0, 1792, 1794, 3, 88, 44, 2, 1793, 1787, 1, 0, 0, 0, 1794, 1797, 1, 0, 0, 0, 1795, 1793, 1, 0, 0, 0, 1795, 1796, 1, 0, 0, 0, 1796, 89, 1, 0, 0, 0, 1797, 1795, 1, 0, 0, 0, 1798, 1808, 3, 98, 49, 0, 1799, 1808, 3, 94, 47, 0, 1800, 1801, 5, 258, 0, 0, 1801, 1808, 3, 214, 107, 0, 1802, 1808, 3, 204, 102, 0, 1803, 1804, 5, 2, 0, 0, 1804, 1805, 3, 36, 18, 0, 1805, 1806, 5, 3, 0, 0, 1806, 1808, 1, 0, 0, 0, 1807, 1798, 1, 0, 0, 0, 1807, 1799, 1, 0, 0, 0, 1807, 1800, 1, 0, 0, 0, 1807, 1802, 1, 0, 0, 0, 1807, 1803, 1, 0, 0, 0, 1808, 91, 1, 0, 0, 0, 1809, 1811, 3, 236, 118, 0, 1810, 1812, 7, 14, 0, 0, 1811, 1810, 1, 0, 0, 0, 1811, 1812, 1, 0, 0, 0, 1812, 1815, 1, 0, 0, 0, 1813, 1814, 5, 174, 0, 0, 1814, 1816, 7, 15, 0, 0, 1815, 1813, 1, 0, 0, 0, 1815, 1816, 1, 0, 0, 0, 1816, 93, 1, 0, 0, 0, 1817, 1819, 3, 130, 65, 0, 1818, 1820, 3, 96, 48, 0, 1819, 1818, 1, 0, 0, 0, 1820, 1821, 1, 0, 0, 0, 1821, 1819, 1, 0, 0, 0, 1821, 1822, 1, 0, 0, 0, 1822, 95, 1, 0, 0, 0, 1823, 1825, 3, 100, 50, 0, 1824, 1826, 3, 122, 61, 0, 1825, 1824, 1, 0, 0, 0, 1825, 1826, 1, 0, 0, 0, 1826, 1827, 1, 0, 0, 0, 1827, 1828, 3, 84, 42, 0, 1828, 1851, 1, 0, 0, 0, 1829, 1833, 3, 102, 51, 0, 1830, 1832, 3, 172, 86, 0, 1831, 1830, 1, 0, 0, 0, 1832, 1835, 1, 0, 0, 0, 1833, 1831, 1, 0, 0, 0, 1833, 1834, 1, 0, 0, 0, 1834, 1837, 1, 0, 0, 0, 1835, 1833, 1, 0, 0, 0, 1836, 1838, 3, 122, 61, 0, 1837, 1836, 1, 0, 0, 0, 1837, 1838, 1, 0, 0, 0, 1838, 1840, 1, 0, 0, 0, 1839, 1841, 3, 134, 67, 0, 1840, 1839, 1, 0, 0, 0, 1840, 1841, 1, 0, 0, 0, 1841, 1843, 1, 0, 0, 0, 1842, 1844, 3, 124, 62, 0, 1843, 1842, 1, 0, 0, 0, 1843, 1844, 1, 0, 0, 0, 1844, 1846, 1, 0, 0, 0, 1845, 1847, 3, 306, 153, 0, 1846, 1845, 1, 0, 0, 0, 1846, 1847, 1, 0, 0, 0, 1847, 1848, 1, 0, 0, 0, 1848, 1849, 3, 84, 42, 0, 1849, 1851, 1, 0, 0, 0, 1850, 1823, 1, 0, 0, 0, 1850, 1829, 1, 0, 0, 0, 1851, 97, 1, 0, 0, 0, 1852, 1854, 3, 100, 50, 0, 1853, 1855, 3, 130, 65, 0, 1854, 1853, 1, 0, 0, 0, 1854, 1855, 1, 0, 0, 0, 1855, 1859, 1, 0, 0, 0, 1856, 1858, 3, 172, 86, 0, 1857, 1856, 1, 0, 0, 0, 1858, 1861, 1, 0, 0, 0, 1859, 1857, 1, 0, 0, 0, 1859, 1860, 1, 0, 0, 0, 1860, 1863, 1, 0, 0, 0, 1861, 1859, 1, 0, 0, 0, 1862, 1864, 3, 122, 61, 0, 1863, 1862, 1, 0, 0, 0, 1863, 1864, 1, 0, 0, 0, 1864, 1866, 1, 0, 0, 0, 1865, 1867, 3, 134, 67, 0, 1866, 1865, 1, 0, 0, 0, 1866, 1867, 1, 0, 0, 0, 1867, 1869, 1, 0, 0, 0, 1868, 1870, 3, 124, 62, 0, 1869, 1868, 1, 0, 0, 0, 1869, 1870, 1, 0, 0, 0, 1870, 1872, 1, 0, 0, 0, 1871, 1873, 3, 306, 153, 0, 1872, 1871, 1, 0, 0, 0, 1872, 1873, 1, 0, 0, 0, 1873, 1897, 1, 0, 0, 0, 1874, 1876, 3, 102, 51, 0, 1875, 1877, 3, 130, 65, 0, 1876, 1875, 1, 0, 0, 0, 1876, 1877, 1, 0, 0, 0, 1877, 1881, 1, 0, 0, 0, 1878, 1880, 3, 172, 86, 0, 1879, 1878, 1, 0, 0, 0, 1880, 1883, 1, 0, 0, 0, 1881, 1879, 1, 0, 0, 0, 1881, 1882, 1, 0, 0, 0, 1882, 1885, 1, 0, 0, 0, 1883, 1881, 1, 0, 0, 0, 1884, 1886, 3, 122, 61, 0, 1885, 1884, 1, 0, 0, 0, 1885, 1886, 1, 0, 0, 0, 1886, 1888, 1, 0, 0, 0, 1887, 1889, 3, 134, 67, 0, 1888, 1887, 1, 0, 0, 0, 1888, 1889, 1, 0, 0, 0, 1889, 1891, 1, 0, 0, 0, 1890, 1892, 3, 124, 62, 0, 1891, 1890, 1, 0, 0, 0, 1891, 1892, 1, 0, 0, 0, 1892, 1894, 1, 0, 0, 0, 1893, 1895, 3, 306, 153, 0, 1894, 1893, 1, 0, 0, 0, 1894, 1895, 1, 0, 0, 0, 1895, 1897, 1, 0, 0, 0, 1896, 1852, 1, 0, 0, 0, 1896, 1874, 1, 0, 0, 0, 1897, 99, 1, 0, 0, 0, 1898, 1899, 5, 233, 0, 0, 1899, 1900, 5, 275, 0, 0, 1900, 1902, 5, 2, 0, 0, 1901, 1903, 3, 174, 87, 0, 1902, 1901, 1, 0, 0, 0, 1902, 1903, 1, 0, 0, 0, 1903, 1904, 1, 0, 0, 0, 1904, 1905, 3, 238, 119, 0, 1905, 1906, 5, 3, 0, 0, 1906, 1918, 1, 0, 0, 0, 1907, 1909, 5, 154, 0, 0, 1908, 1910, 3, 174, 87, 0, 1909, 1908, 1, 0, 0, 0, 1909, 1910, 1, 0, 0, 0, 1910, 1911, 1, 0, 0, 0, 1911, 1918, 3, 238, 119, 0, 1912, 1914, 5, 210, 0, 0, 1913, 1915, 3, 174, 87, 0, 1914, 1913, 1, 0, 0, 0, 1914, 1915, 1, 0, 0, 0, 1915, 1916, 1, 0, 0, 0, 1916, 1918, 3, 238, 119, 0, 1917, 1898, 1, 0, 0, 0, 1917, 1907, 1, 0, 0, 0, 1917, 1912, 1, 0, 0, 0, 1918, 1920, 1, 0, 0, 0, 1919, 1921, 3, 210, 105, 0, 1920, 1919, 1, 0, 0, 0, 1920, 1921, 1, 0, 0, 0, 1921, 1924, 1, 0, 0, 0, 1922, 1923, 5, 208, 0, 0, 1923, 1925, 3, 338, 169, 0, 1924, 1922, 1, 0, 0, 0, 1924, 1925, 1, 0, 0, 0, 1925, 1926, 1, 0, 0, 0, 1926, 1927, 5, 293, 0, 0, 1927, 1940, 3, 338, 169, 0, 1928, 1938, 5, 20, 0, 0, 1929, 1939, 3, 192, 96, 0, 1930, 1939, 3, 288, 144, 0, 1931, 1934, 5, 2, 0, 0, 1932, 1935, 3, 192, 96, 0, 1933, 1935, 3, 288, 144, 0, 1934, 1932, 1, 0, 0, 0, 1934, 1933, 1, 0, 0, 0, 1935, 1936, 1, 0, 0, 0, 1936, 1937, 5, 3, 0, 0, 1937, 1939, 1, 0, 0, 0, 1938, 1929, 1, 0, 0, 0, 1938, 1930, 1, 0, 0, 0, 1938, 1931, 1, 0, 0, 0, 1939, 1941, 1, 0, 0, 0, 1940, 1928, 1, 0, 0, 0, 1940, 1941, 1, 0, 0, 0, 1941, 1943, 1, 0, 0, 0, 1942, 1944, 3, 210, 105, 0, 1943, 1942, 1, 0, 0, 0, 1943, 1944, 1, 0, 0, 0, 1944, 1947, 1, 0, 0, 0, 1945, 1946, 5, 207, 0, 0, 1946, 1948, 3, 338, 169, 0, 1947, 1945, 1, 0, 0, 0, 1947, 1948, 1, 0, 0, 0, 1948, 101, 1, 0, 0, 0, 1949, 1953, 5, 233, 0, 0, 1950, 1952, 3, 126, 63, 0, 1951, 1950, 1, 0, 0, 0, 1952, 1955, 1, 0, 0, 0, 1953, 1951, 1, 0, 0, 0, 1953, 1954, 1, 0, 0, 0, 1954, 1957, 1, 0, 0, 0, 1955, 1953, 1, 0, 0, 0, 1956, 1958, 3, 174, 87, 0, 1957, 1956, 1, 0, 0, 0, 1957, 1958, 1, 0, 0, 0, 1958, 1959, 1, 0, 0, 0, 1959, 1960, 3, 226, 113, 0, 1960, 103, 1, 0, 0, 0, 1961, 1962, 5, 239, 0, 0, 1962, 1963, 3, 118, 59, 0, 1963, 105, 1, 0, 0, 0, 1964, 1965, 5, 300, 0, 0, 1965, 1968, 5, 155, 0, 0, 1966, 1967, 5, 14, 0, 0, 1967, 1969, 3, 240, 120, 0, 1968, 1966, 1, 0, 0, 0, 1968, 1969, 1, 0, 0, 0, 1969, 1970, 1, 0, 0, 0, 1970, 1971, 5, 265, 0, 0, 1971, 1972, 3, 112, 56, 0, 1972, 107, 1, 0, 0, 0, 1973, 1974, 5, 300, 0, 0, 1974, 1975, 5, 172, 0, 0, 1975, 1978, 5, 155, 0, 0, 1976, 1977, 5, 28, 0, 0, 1977, 1979, 5, 261, 0, 0, 1978, 1976, 1, 0, 0, 0, 1978, 1979, 1, 0, 0, 0, 1979, 1982, 1, 0, 0, 0, 1980, 1981, 5, 14, 0, 0, 1981, 1983, 3, 240, 120, 0, 1982, 1980, 1, 0, 0, 0, 1982, 1983, 1, 0, 0, 0, 1983, 1984, 1, 0, 0, 0, 1984, 1985, 5, 265, 0, 0, 1985, 1986, 3, 114, 57, 0, 1986, 109, 1, 0, 0, 0, 1987, 1988, 5, 300, 0, 0, 1988, 1989, 5, 172, 0, 0, 1989, 1990, 5, 155, 0, 0, 1990, 1991, 5, 28, 0, 0, 1991, 1994, 5, 247, 0, 0, 1992, 1993, 5, 14, 0, 0, 1993, 1995, 3, 240, 120, 0, 1994, 1992, 1, 0, 0, 0, 1994, 1995, 1, 0, 0, 0, 1995, 1996, 1, 0, 0, 0, 1996, 1997, 5, 265, 0, 0, 1997, 1998, 3, 116, 58, 0, 1998, 111, 1, 0, 0, 0, 1999, 2007, 5, 72, 0, 0, 2000, 2001, 5, 290, 0, 0, 2001, 2002, 5, 239, 0, 0, 2002, 2007, 5, 318, 0, 0, 2003, 2004, 5, 290, 0, 0, 2004, 2005, 5, 239, 0, 0, 2005, 2007, 3, 118, 59, 0, 2006, 1999, 1, 0, 0, 0, 2006, 2000, 1, 0, 0, 0, 2006, 2003, 1, 0, 0, 0, 2007, 113, 1, 0, 0, 0, 2008, 2009, 5, 129, 0, 0, 2009, 2027, 5, 318, 0, 0, 2010, 2011, 5, 129, 0, 0, 2011, 2012, 5, 2, 0, 0, 2012, 2013, 3, 212, 106, 0, 2013, 2014, 5, 3, 0, 0, 2014, 2015, 5, 294, 0, 0, 2015, 2016, 5, 2, 0, 0, 2016, 2021, 3, 236, 118, 0, 2017, 2018, 5, 4, 0, 0, 2018, 2020, 3, 236, 118, 0, 2019, 2017, 1, 0, 0, 0, 2020, 2023, 1, 0, 0, 0, 2021, 2019, 1, 0, 0, 0, 2021, 2022, 1, 0, 0, 0, 2022, 2024, 1, 0, 0, 0, 2023, 2021, 1, 0, 0, 0, 2024, 2025, 5, 3, 0, 0, 2025, 2027, 1, 0, 0, 0, 2026, 2008, 1, 0, 0, 0, 2026, 2010, 1, 0, 0, 0, 2027, 115, 1, 0, 0, 0, 2028, 2033, 5, 72, 0, 0, 2029, 2030, 5, 290, 0, 0, 2030, 2031, 5, 239, 0, 0, 2031, 2033, 3, 118, 59, 0, 2032, 2028, 1, 0, 0, 0, 2032, 2029, 1, 0, 0, 0, 2033, 117, 1, 0, 0, 0, 2034, 2039, 3, 120, 60, 0, 2035, 2036, 5, 4, 0, 0, 2036, 2038, 3, 120, 60, 0, 2037, 2035, 1, 0, 0, 0, 2038, 2041, 1, 0, 0, 0, 2039, 2037, 1, 0, 0, 0, 2039, 2040, 1, 0, 0, 0, 2040, 119, 1, 0, 0, 0, 2041, 2039, 1, 0, 0, 0, 2042, 2043, 3, 214, 107, 0, 2043, 2044, 5, 308, 0, 0, 2044, 2045, 3, 236, 118, 0, 2045, 121, 1, 0, 0, 0, 2046, 2047, 5, 301, 0, 0, 2047, 2048, 3, 240, 120, 0, 2048, 123, 1, 0, 0, 0, 2049, 2050, 5, 116, 0, 0, 2050, 2051, 3, 240, 120, 0, 2051, 125, 1, 0, 0, 0, 2052, 2053, 5, 328, 0, 0, 2053, 2060, 3, 128, 64, 0, 2054, 2056, 5, 4, 0, 0, 2055, 2054, 1, 0, 0, 0, 2055, 2056, 1, 0, 0, 0, 2056, 2057, 1, 0, 0, 0, 2057, 2059, 3, 128, 64, 0, 2058, 2055, 1, 0, 0, 0, 2059, 2062, 1, 0, 0, 0, 2060, 2058, 1, 0, 0, 0, 2060, 2061, 1, 0, 0, 0, 2061, 2063, 1, 0, 0, 0, 2062, 2060, 1, 0, 0, 0, 2063, 2064, 5, 329, 0, 0, 2064, 127, 1, 0, 0, 0, 2065, 2079, 3, 326, 163, 0, 2066, 2067, 3, 326, 163, 0, 2067, 2068, 5, 2, 0, 0, 2068, 2073, 3, 248, 124, 0, 2069, 2070, 5, 4, 0, 0, 2070, 2072, 3, 248, 124, 0, 2071, 2069, 1, 0, 0, 0, 2072, 2075, 1, 0, 0, 0, 2073, 2071, 1, 0, 0, 0, 2073, 2074, 1, 0, 0, 0, 2074, 2076, 1, 0, 0, 0, 2075, 2073, 1, 0, 0, 0, 2076, 2077, 5, 3, 0, 0, 2077, 2079, 1, 0, 0, 0, 2078, 2065, 1, 0, 0, 0, 2078, 2066, 1, 0, 0, 0, 2079, 129, 1, 0, 0, 0, 2080, 2081, 5, 107, 0, 0, 2081, 2086, 3, 176, 88, 0, 2082, 2083, 5, 4, 0, 0, 2083, 2085, 3, 176, 88, 0, 2084, 2082, 1, 0, 0, 0, 2085, 2088, 1, 0, 0, 0, 2086, 2084, 1, 0, 0, 0, 2086, 2087, 1, 0, 0, 0, 2087, 2092, 1, 0, 0, 0, 2088, 2086, 1, 0, 0, 0, 2089, 2091, 3, 172, 86, 0, 2090, 2089, 1, 0, 0, 0, 2091, 2094, 1, 0, 0, 0, 2092, 2090, 1, 0, 0, 0, 2092, 2093, 1, 0, 0, 0, 2093, 2096, 1, 0, 0, 0, 2094, 2092, 1, 0, 0, 0, 2095, 2097, 3, 144, 72, 0, 2096, 2095, 1, 0, 0, 0, 2096, 2097, 1, 0, 0, 0, 2097, 2099, 1, 0, 0, 0, 2098, 2100, 3, 150, 75, 0, 2099, 2098, 1, 0, 0, 0, 2099, 2100, 1, 0, 0, 0, 2100, 131, 1, 0, 0, 0, 2101, 2103, 5, 103, 0, 0, 2102, 2101, 1, 0, 0, 0, 2102, 2103, 1, 0, 0, 0, 2103, 2104, 1, 0, 0, 0, 2104, 2105, 7, 16, 0, 0, 2105, 2106, 5, 20, 0, 0, 2106, 2107, 5, 175, 0, 0, 2107, 2116, 3, 342, 171, 0, 2108, 2110, 5, 103, 0, 0, 2109, 2108, 1, 0, 0, 0, 2109, 2110, 1, 0, 0, 0, 2110, 2111, 1, 0, 0, 0, 2111, 2112, 7, 17, 0, 0, 2112, 2113, 5, 20, 0, 0, 2113, 2114, 5, 175, 0, 0, 2114, 2116, 3, 244, 122, 0, 2115, 2102, 1, 0, 0, 0, 2115, 2109, 1, 0, 0, 0, 2116, 133, 1, 0, 0, 0, 2117, 2118, 5, 114, 0, 0, 2118, 2119, 5, 28, 0, 0, 2119, 2124, 3, 136, 68, 0, 2120, 2121, 5, 4, 0, 0, 2121, 2123, 3, 136, 68, 0, 2122, 2120, 1, 0, 0, 0, 2123, 2126, 1, 0, 0, 0, 2124, 2122, 1, 0, 0, 0, 2124, 2125, 1, 0, 0, 0, 2125, 2157, 1, 0, 0, 0, 2126, 2124, 1, 0, 0, 0, 2127, 2128, 5, 114, 0, 0, 2128, 2129, 5, 28, 0, 0, 2129, 2134, 3, 236, 118, 0, 2130, 2131, 5, 4, 0, 0, 2131, 2133, 3, 236, 118, 0, 2132, 2130, 1, 0, 0, 0, 2133, 2136, 1, 0, 0, 0, 2134, 2132, 1, 0, 0, 0, 2134, 2135, 1, 0, 0, 0, 2135, 2154, 1, 0, 0, 0, 2136, 2134, 1, 0, 0, 0, 2137, 2138, 5, 303, 0, 0, 2138, 2155, 5, 226, 0, 0, 2139, 2140, 5, 303, 0, 0, 2140, 2155, 5, 55, 0, 0, 2141, 2142, 5, 115, 0, 0, 2142, 2143, 5, 241, 0, 0, 2143, 2144, 5, 2, 0, 0, 2144, 2149, 3, 142, 71, 0, 2145, 2146, 5, 4, 0, 0, 2146, 2148, 3, 142, 71, 0, 2147, 2145, 1, 0, 0, 0, 2148, 2151, 1, 0, 0, 0, 2149, 2147, 1, 0, 0, 0, 2149, 2150, 1, 0, 0, 0, 2150, 2152, 1, 0, 0, 0, 2151, 2149, 1, 0, 0, 0, 2152, 2153, 5, 3, 0, 0, 2153, 2155, 1, 0, 0, 0, 2154, 2137, 1, 0, 0, 0, 2154, 2139, 1, 0, 0, 0, 2154, 2141, 1, 0, 0, 0, 2154, 2155, 1, 0, 0, 0, 2155, 2157, 1, 0, 0, 0, 2156, 2117, 1, 0, 0, 0, 2156, 2127, 1, 0, 0, 0, 2157, 135, 1, 0, 0, 0, 2158, 2161, 3, 138, 69, 0, 2159, 2161, 3, 236, 118, 0, 2160, 2158, 1, 0, 0, 0, 2160, 2159, 1, 0, 0, 0, 2161, 137, 1, 0, 0, 0, 2162, 2163, 7, 18, 0, 0, 2163, 2164, 5, 2, 0, 0, 2164, 2169, 3, 142, 71, 0, 2165, 2166, 5, 4, 0, 0, 2166, 2168, 3, 142, 71, 0, 2167, 2165, 1, 0, 0, 0, 2168, 2171, 1, 0, 0, 0, 2169, 2167, 1, 0, 0, 0, 2169, 2170, 1, 0, 0, 0, 2170, 2172, 1, 0, 0, 0, 2171, 2169, 1, 0, 0, 0, 2172, 2173, 5, 3, 0, 0, 2173, 2188, 1, 0, 0, 0, 2174, 2175, 5, 115, 0, 0, 2175, 2176, 5, 241, 0, 0, 2176, 2177, 5, 2, 0, 0, 2177, 2182, 3, 140, 70, 0, 2178, 2179, 5, 4, 0, 0, 2179, 2181, 3, 140, 70, 0, 2180, 2178, 1, 0, 0, 0, 2181, 2184, 1, 0, 0, 0, 2182, 2180, 1, 0, 0, 0, 2182, 2183, 1, 0, 0, 0, 2183, 2185, 1, 0, 0, 0, 2184, 2182, 1, 0, 0, 0, 2185, 2186, 5, 3, 0, 0, 2186, 2188, 1, 0, 0, 0, 2187, 2162, 1, 0, 0, 0, 2187, 2174, 1, 0, 0, 0, 2188, 139, 1, 0, 0, 0, 2189, 2192, 3, 138, 69, 0, 2190, 2192, 3, 142, 71, 0, 2191, 2189, 1, 0, 0, 0, 2191, 2190, 1, 0, 0, 0, 2192, 141, 1, 0, 0, 0, 2193, 2202, 5, 2, 0, 0, 2194, 2199, 3, 236, 118, 0, 2195, 2196, 5, 4, 0, 0, 2196, 2198, 3, 236, 118, 0, 2197, 2195, 1, 0, 0, 0, 2198, 2201, 1, 0, 0, 0, 2199, 2197, 1, 0, 0, 0, 2199, 2200, 1, 0, 0, 0, 2200, 2203, 1, 0, 0, 0, 2201, 2199, 1, 0, 0, 0, 2202, 2194, 1, 0, 0, 0, 2202, 2203, 1, 0, 0, 0, 2203, 2204, 1, 0, 0, 0, 2204, 2207, 5, 3, 0, 0, 2205, 2207, 3, 236, 118, 0, 2206, 2193, 1, 0, 0, 0, 2206, 2205, 1, 0, 0, 0, 2207, 143, 1, 0, 0, 0, 2208, 2209, 5, 196, 0, 0, 2209, 2210, 5, 2, 0, 0, 2210, 2211, 3, 226, 113, 0, 2211, 2212, 5, 103, 0, 0, 2212, 2213, 3, 146, 73, 0, 2213, 2214, 5, 122, 0, 0, 2214, 2215, 5, 2, 0, 0, 2215, 2220, 3, 148, 74, 0, 2216, 2217, 5, 4, 0, 0, 2217, 2219, 3, 148, 74, 0, 2218, 2216, 1, 0, 0, 0, 2219, 2222, 1, 0, 0, 0, 2220, 2218, 1, 0, 0, 0, 2220, 2221, 1, 0, 0, 0, 2221, 2223, 1, 0, 0, 0, 2222, 2220, 1, 0, 0, 0, 2223, 2224, 5, 3, 0, 0, 2224, 2225, 5, 3, 0, 0, 2225, 145, 1, 0, 0, 0, 2226, 2239, 3, 326, 163, 0, 2227, 2228, 5, 2, 0, 0, 2228, 2233, 3, 326, 163, 0, 2229, 2230, 5, 4, 0, 0, 2230, 2232, 3, 326, 163, 0, 2231, 2229, 1, 0, 0, 0, 2232, 2235, 1, 0, 0, 0, 2233, 2231, 1, 0, 0, 0, 2233, 2234, 1, 0, 0, 0, 2234, 2236, 1, 0, 0, 0, 2235, 2233, 1, 0, 0, 0, 2236, 2237, 5, 3, 0, 0, 2237, 2239, 1, 0, 0, 0, 2238, 2226, 1, 0, 0, 0, 2238, 2227, 1, 0, 0, 0, 2239, 147, 1, 0, 0, 0, 2240, 2245, 3, 236, 118, 0, 2241, 2243, 5, 20, 0, 0, 2242, 2241, 1, 0, 0, 0, 2242, 2243, 1, 0, 0, 0, 2243, 2244, 1, 0, 0, 0, 2244, 2246, 3, 326, 163, 0, 2245, 2242, 1, 0, 0, 0, 2245, 2246, 1, 0, 0, 0, 2246, 149, 1, 0, 0, 0, 2247, 2249, 5, 288, 0, 0, 2248, 2250, 3, 152, 76, 0, 2249, 2248, 1, 0, 0, 0, 2249, 2250, 1, 0, 0, 0, 2250, 2251, 1, 0, 0, 0, 2251, 2252, 5, 2, 0, 0, 2252, 2253, 3, 154, 77, 0, 2253, 2258, 5, 3, 0, 0, 2254, 2256, 5, 20, 0, 0, 2255, 2254, 1, 0, 0, 0, 2255, 2256, 1, 0, 0, 0, 2256, 2257, 1, 0, 0, 0, 2257, 2259, 3, 326, 163, 0, 2258, 2255, 1, 0, 0, 0, 2258, 2259, 1, 0, 0, 0, 2259, 151, 1, 0, 0, 0, 2260, 2261, 7, 19, 0, 0, 2261, 2262, 5, 174, 0, 0, 2262, 153, 1, 0, 0, 0, 2263, 2266, 3, 156, 78, 0, 2264, 2266, 3, 158, 79, 0, 2265, 2263, 1, 0, 0, 0, 2265, 2264, 1, 0, 0, 0, 2266, 155, 1, 0, 0, 0, 2267, 2268, 3, 162, 81, 0, 2268, 2269, 5, 103, 0, 0, 2269, 2270, 3, 164, 82, 0, 2270, 2271, 5, 122, 0, 0, 2271, 2272, 5, 2, 0, 0, 2272, 2277, 3, 166, 83, 0, 2273, 2274, 5, 4, 0, 0, 2274, 2276, 3, 166, 83, 0, 2275, 2273, 1, 0, 0, 0, 2276, 2279, 1, 0, 0, 0, 2277, 2275, 1, 0, 0, 0, 2277, 2278, 1, 0, 0, 0, 2278, 2280, 1, 0, 0, 0, 2279, 2277, 1, 0, 0, 0, 2280, 2281, 5, 3, 0, 0, 2281, 157, 1, 0, 0, 0, 2282, 2283, 5, 2, 0, 0, 2283, 2288, 3, 162, 81, 0, 2284, 2285, 5, 4, 0, 0, 2285, 2287, 3, 162, 81, 0, 2286, 2284, 1, 0, 0, 0, 2287, 2290, 1, 0, 0, 0, 2288, 2286, 1, 0, 0, 0, 2288, 2289, 1, 0, 0, 0, 2289, 2291, 1, 0, 0, 0, 2290, 2288, 1, 0, 0, 0, 2291, 2292, 5, 3, 0, 0, 2292, 2293, 5, 103, 0, 0, 2293, 2294, 3, 164, 82, 0, 2294, 2295, 5, 122, 0, 0, 2295, 2296, 5, 2, 0, 0, 2296, 2301, 3, 160, 80, 0, 2297, 2298, 5, 4, 0, 0, 2298, 2300, 3, 160, 80, 0, 2299, 2297, 1, 0, 0, 0, 2300, 2303, 1, 0, 0, 0, 2301, 2299, 1, 0, 0, 0, 2301, 2302, 1, 0, 0, 0, 2302, 2304, 1, 0, 0, 0, 2303, 2301, 1, 0, 0, 0, 2304, 2305, 5, 3, 0, 0, 2305, 159, 1, 0, 0, 0, 2306, 2307, 5, 2, 0, 0, 2307, 2312, 3, 168, 84, 0, 2308, 2309, 5, 4, 0, 0, 2309, 2311, 3, 168, 84, 0, 2310, 2308, 1, 0, 0, 0, 2311, 2314, 1, 0, 0, 0, 2312, 2310, 1, 0, 0, 0, 2312, 2313, 1, 0, 0, 0, 2313, 2315, 1, 0, 0, 0, 2314, 2312, 1, 0, 0, 0, 2315, 2317, 5, 3, 0, 0, 2316, 2318, 3, 170, 85, 0, 2317, 2316, 1, 0, 0, 0, 2317, 2318, 1, 0, 0, 0, 2318, 161, 1, 0, 0, 0, 2319, 2320, 3, 326, 163, 0, 2320, 163, 1, 0, 0, 0, 2321, 2322, 3, 326, 163, 0, 2322, 165, 1, 0, 0, 0, 2323, 2325, 3, 168, 84, 0, 2324, 2326, 3, 170, 85, 0, 2325, 2324, 1, 0, 0, 0, 2325, 2326, 1, 0, 0, 0, 2326, 167, 1, 0, 0, 0, 2327, 2328, 3, 214, 107, 0, 2328, 169, 1, 0, 0, 0, 2329, 2331, 5, 20, 0, 0, 2330, 2329, 1, 0, 0, 0, 2330, 2331, 1, 0, 0, 0, 2331, 2332, 1, 0, 0, 0, 2332, 2333, 3, 326, 163, 0, 2333, 171, 1, 0, 0, 0, 2334, 2335, 5, 138, 0, 0, 2335, 2337, 5, 296, 0, 0, 2336, 2338, 5, 184, 0, 0, 2337, 2336, 1, 0, 0, 0, 2337, 2338, 1, 0, 0, 0, 2338, 2339, 1, 0, 0, 0, 2339, 2340, 3, 320, 160, 0, 2340, 2349, 5, 2, 0, 0, 2341, 2346, 3, 236, 118, 0, 2342, 2343, 5, 4, 0, 0, 2343, 2345, 3, 236, 118, 0, 2344, 2342, 1, 0, 0, 0, 2345, 2348, 1, 0, 0, 0, 2346, 2344, 1, 0, 0, 0, 2346, 2347, 1, 0, 0, 0, 2347, 2350, 1, 0, 0, 0, 2348, 2346, 1, 0, 0, 0, 2349, 2341, 1, 0, 0, 0, 2349, 2350, 1, 0, 0, 0, 2350, 2351, 1, 0, 0, 0, 2351, 2352, 5, 3, 0, 0, 2352, 2364, 3, 326, 163, 0, 2353, 2355, 5, 20, 0, 0, 2354, 2353, 1, 0, 0, 0, 2354, 2355, 1, 0, 0, 0, 2355, 2356, 1, 0, 0, 0, 2356, 2361, 3, 326, 163, 0, 2357, 2358, 5, 4, 0, 0, 2358, 2360, 3, 326, 163, 0, 2359, 2357, 1, 0, 0, 0, 2360, 2363, 1, 0, 0, 0, 2361, 2359, 1, 0, 0, 0, 2361, 2362, 1, 0, 0, 0, 2362, 2365, 1, 0, 0, 0, 2363, 2361, 1, 0, 0, 0, 2364, 2354, 1, 0, 0, 0, 2364, 2365, 1, 0, 0, 0, 2365, 173, 1, 0, 0, 0, 2366, 2367, 7, 20, 0, 0, 2367, 175, 1, 0, 0, 0, 2368, 2370, 5, 138, 0, 0, 2369, 2368, 1, 0, 0, 0, 2369, 2370, 1, 0, 0, 0, 2370, 2371, 1, 0, 0, 0, 2371, 2375, 3, 202, 101, 0, 2372, 2374, 3, 178, 89, 0, 2373, 2372, 1, 0, 0, 0, 2374, 2377, 1, 0, 0, 0, 2375, 2373, 1, 0, 0, 0, 2375, 2376, 1, 0, 0, 0, 2376, 177, 1, 0, 0, 0, 2377, 2375, 1, 0, 0, 0, 2378, 2382, 3, 180, 90, 0, 2379, 2382, 3, 144, 72, 0, 2380, 2382, 3, 150, 75, 0, 2381, 2378, 1, 0, 0, 0, 2381, 2379, 1, 0, 0, 0, 2381, 2380, 1, 0, 0, 0, 2382, 179, 1, 0, 0, 0, 2383, 2384, 3, 182, 91, 0, 2384, 2386, 5, 135, 0, 0, 2385, 2387, 5, 138, 0, 0, 2386, 2385, 1, 0, 0, 0, 2386, 2387, 1, 0, 0, 0, 2387, 2388, 1, 0, 0, 0, 2388, 2390, 3, 202, 101, 0, 2389, 2391, 3, 184, 92, 0, 2390, 2389, 1, 0, 0, 0, 2390, 2391, 1, 0, 0, 0, 2391, 2401, 1, 0, 0, 0, 2392, 2393, 5, 170, 0, 0, 2393, 2394, 3, 182, 91, 0, 2394, 2396, 5, 135, 0, 0, 2395, 2397, 5, 138, 0, 0, 2396, 2395, 1, 0, 0, 0, 2396, 2397, 1, 0, 0, 0, 2397, 2398, 1, 0, 0, 0, 2398, 2399, 3, 202, 101, 0, 2399, 2401, 1, 0, 0, 0, 2400, 2383, 1, 0, 0, 0, 2400, 2392, 1, 0, 0, 0, 2401, 181, 1, 0, 0, 0, 2402, 2404, 5, 126, 0, 0, 2403, 2402, 1, 0, 0, 0, 2403, 2404, 1, 0, 0, 0, 2404, 2427, 1, 0, 0, 0, 2405, 2427, 5, 54, 0, 0, 2406, 2408, 5, 141, 0, 0, 2407, 2409, 5, 184, 0, 0, 2408, 2407, 1, 0, 0, 0, 2408, 2409, 1, 0, 0, 0, 2409, 2427, 1, 0, 0, 0, 2410, 2412, 5, 141, 0, 0, 2411, 2410, 1, 0, 0, 0, 2411, 2412, 1, 0, 0, 0, 2412, 2413, 1, 0, 0, 0, 2413, 2427, 5, 234, 0, 0, 2414, 2416, 5, 221, 0, 0, 2415, 2417, 5, 184, 0, 0, 2416, 2415, 1, 0, 0, 0, 2416, 2417, 1, 0, 0, 0, 2417, 2427, 1, 0, 0, 0, 2418, 2420, 5, 108, 0, 0, 2419, 2421, 5, 184, 0, 0, 2420, 2419, 1, 0, 0, 0, 2420, 2421, 1, 0, 0, 0, 2421, 2427, 1, 0, 0, 0, 2422, 2424, 5, 141, 0, 0, 2423, 2422, 1, 0, 0, 0, 2423, 2424, 1, 0, 0, 0, 2424, 2425, 1, 0, 0, 0, 2425, 2427, 5, 15, 0, 0, 2426, 2403, 1, 0, 0, 0, 2426, 2405, 1, 0, 0, 0, 2426, 2406, 1, 0, 0, 0, 2426, 2411, 1, 0, 0, 0, 2426, 2414, 1, 0, 0, 0, 2426, 2418, 1, 0, 0, 0, 2426, 2423, 1, 0, 0, 0, 2427, 183, 1, 0, 0, 0, 2428, 2429, 5, 177, 0, 0, 2429, 2433, 3, 240, 120, 0, 2430, 2431, 5, 293, 0, 0, 2431, 2433, 3, 190, 95, 0, 2432, 2428, 1, 0, 0, 0, 2432, 2430, 1, 0, 0, 0, 2433, 185, 1, 0, 0, 0, 2434, 2435, 5, 260, 0, 0, 2435, 2437, 5, 2, 0, 0, 2436, 2438, 3, 188, 94, 0, 2437, 2436, 1, 0, 0, 0, 2437, 2438, 1, 0, 0, 0, 2438, 2439, 1, 0, 0, 0, 2439, 2444, 5, 3, 0, 0, 2440, 2441, 5, 215, 0, 0, 2441, 2442, 5, 2, 0, 0, 2442, 2443, 5, 335, 0, 0, 2443, 2445, 5, 3, 0, 0, 2444, 2440, 1, 0, 0, 0, 2444, 2445, 1, 0, 0, 0, 2445, 187, 1, 0, 0, 0, 2446, 2448, 5, 317, 0, 0, 2447, 2446, 1, 0, 0, 0, 2447, 2448, 1, 0, 0, 0, 2448, 2449, 1, 0, 0, 0, 2449, 2450, 7, 21, 0, 0, 2450, 2471, 5, 195, 0, 0, 2451, 2452, 3, 236, 118, 0, 2452, 2453, 5, 228, 0, 0, 2453, 2471, 1, 0, 0, 0, 2454, 2455, 5, 26, 0, 0, 2455, 2456, 5, 335, 0, 0, 2456, 2457, 5, 183, 0, 0, 2457, 2458, 5, 175, 0, 0, 2458, 2467, 5, 335, 0, 0, 2459, 2465, 5, 177, 0, 0, 2460, 2466, 3, 326, 163, 0, 2461, 2462, 3, 320, 160, 0, 2462, 2463, 5, 2, 0, 0, 2463, 2464, 5, 3, 0, 0, 2464, 2466, 1, 0, 0, 0, 2465, 2460, 1, 0, 0, 0, 2465, 2461, 1, 0, 0, 0, 2466, 2468, 1, 0, 0, 0, 2467, 2459, 1, 0, 0, 0, 2467, 2468, 1, 0, 0, 0, 2468, 2471, 1, 0, 0, 0, 2469, 2471, 3, 236, 118, 0, 2470, 2447, 1, 0, 0, 0, 2470, 2451, 1, 0, 0, 0, 2470, 2454, 1, 0, 0, 0, 2470, 2469, 1, 0, 0, 0, 2471, 189, 1, 0, 0, 0, 2472, 2473, 5, 2, 0, 0, 2473, 2474, 3, 192, 96, 0, 2474, 2475, 5, 3, 0, 0, 2475, 191, 1, 0, 0, 0, 2476, 2481, 3, 322, 161, 0, 2477, 2478, 5, 4, 0, 0, 2478, 2480, 3, 322, 161, 0, 2479, 2477, 1, 0, 0, 0, 2480, 2483, 1, 0, 0, 0, 2481, 2479, 1, 0, 0, 0, 2481, 2482, 1, 0, 0, 0, 2482, 193, 1, 0, 0, 0, 2483, 2481, 1, 0, 0, 0, 2484, 2485, 5, 2, 0, 0, 2485, 2490, 3, 196, 98, 0, 2486, 2487, 5, 4, 0, 0, 2487, 2489, 3, 196, 98, 0, 2488, 2486, 1, 0, 0, 0, 2489, 2492, 1, 0, 0, 0, 2490, 2488, 1, 0, 0, 0, 2490, 2491, 1, 0, 0, 0, 2491, 2493, 1, 0, 0, 0, 2492, 2490, 1, 0, 0, 0, 2493, 2494, 5, 3, 0, 0, 2494, 195, 1, 0, 0, 0, 2495, 2497, 3, 322, 161, 0, 2496, 2498, 7, 14, 0, 0, 2497, 2496, 1, 0, 0, 0, 2497, 2498, 1, 0, 0, 0, 2498, 197, 1, 0, 0, 0, 2499, 2500, 5, 2, 0, 0, 2500, 2505, 3, 200, 100, 0, 2501, 2502, 5, 4, 0, 0, 2502, 2504, 3, 200, 100, 0, 2503, 2501, 1, 0, 0, 0, 2504, 2507, 1, 0, 0, 0, 2505, 2503, 1, 0, 0, 0, 2505, 2506, 1, 0, 0, 0, 2506, 2508, 1, 0, 0, 0, 2507, 2505, 1, 0, 0, 0, 2508, 2509, 5, 3, 0, 0, 2509, 199, 1, 0, 0, 0, 2510, 2512, 3, 326, 163, 0, 2511, 2513, 3, 34, 17, 0, 2512, 2511, 1, 0, 0, 0, 2512, 2513, 1, 0, 0, 0, 2513, 201, 1, 0, 0, 0, 2514, 2516, 3, 214, 107, 0, 2515, 2517, 3, 132, 66, 0, 2516, 2515, 1, 0, 0, 0, 2516, 2517, 1, 0, 0, 0, 2517, 2519, 1, 0, 0, 0, 2518, 2520, 3, 186, 93, 0, 2519, 2518, 1, 0, 0, 0, 2519, 2520, 1, 0, 0, 0, 2520, 2521, 1, 0, 0, 0, 2521, 2522, 3, 208, 104, 0, 2522, 2542, 1, 0, 0, 0, 2523, 2524, 5, 2, 0, 0, 2524, 2525, 3, 36, 18, 0, 2525, 2527, 5, 3, 0, 0, 2526, 2528, 3, 186, 93, 0, 2527, 2526, 1, 0, 0, 0, 2527, 2528, 1, 0, 0, 0, 2528, 2529, 1, 0, 0, 0, 2529, 2530, 3, 208, 104, 0, 2530, 2542, 1, 0, 0, 0, 2531, 2532, 5, 2, 0, 0, 2532, 2533, 3, 176, 88, 0, 2533, 2535, 5, 3, 0, 0, 2534, 2536, 3, 186, 93, 0, 2535, 2534, 1, 0, 0, 0, 2535, 2536, 1, 0, 0, 0, 2536, 2537, 1, 0, 0, 0, 2537, 2538, 3, 208, 104, 0, 2538, 2542, 1, 0, 0, 0, 2539, 2542, 3, 204, 102, 0, 2540, 2542, 3, 206, 103, 0, 2541, 2514, 1, 0, 0, 0, 2541, 2523, 1, 0, 0, 0, 2541, 2531, 1, 0, 0, 0, 2541, 2539, 1, 0, 0, 0, 2541, 2540, 1, 0, 0, 0, 2542, 203, 1, 0, 0, 0, 2543, 2544, 5, 294, 0, 0, 2544, 2549, 3, 236, 118, 0, 2545, 2546, 5, 4, 0, 0, 2546, 2548, 3, 236, 118, 0, 2547, 2545, 1, 0, 0, 0, 2548, 2551, 1, 0, 0, 0, 2549, 2547, 1, 0, 0, 0, 2549, 2550, 1, 0, 0, 0, 2550, 2552, 1, 0, 0, 0, 2551, 2549, 1, 0, 0, 0, 2552, 2553, 3, 208, 104, 0, 2553, 205, 1, 0, 0, 0, 2554, 2555, 3, 318, 159, 0, 2555, 2564, 5, 2, 0, 0, 2556, 2561, 3, 236, 118, 0, 2557, 2558, 5, 4, 0, 0, 2558, 2560, 3, 236, 118, 0, 2559, 2557, 1, 0, 0, 0, 2560, 2563, 1, 0, 0, 0, 2561, 2559, 1, 0, 0, 0, 2561, 2562, 1, 0, 0, 0, 2562, 2565, 1, 0, 0, 0, 2563, 2561, 1, 0, 0, 0, 2564, 2556, 1, 0, 0, 0, 2564, 2565, 1, 0, 0, 0, 2565, 2566, 1, 0, 0, 0, 2566, 2567, 5, 3, 0, 0, 2567, 2568, 3, 208, 104, 0, 2568, 207, 1, 0, 0, 0, 2569, 2571, 5, 20, 0, 0, 2570, 2569, 1, 0, 0, 0, 2570, 2571, 1, 0, 0, 0, 2571, 2572, 1, 0, 0, 0, 2572, 2574, 3, 328, 164, 0, 2573, 2575, 3, 190, 95, 0, 2574, 2573, 1, 0, 0, 0, 2574, 2575, 1, 0, 0, 0, 2575, 2577, 1, 0, 0, 0, 2576, 2570, 1, 0, 0, 0, 2576, 2577, 1, 0, 0, 0, 2577, 209, 1, 0, 0, 0, 2578, 2579, 5, 227, 0, 0, 2579, 2580, 5, 105, 0, 0, 2580, 2581, 5, 236, 0, 0, 2581, 2585, 3, 338, 169, 0, 2582, 2583, 5, 303, 0, 0, 2583, 2584, 5, 237, 0, 0, 2584, 2586, 3, 62, 31, 0, 2585, 2582, 1, 0, 0, 0, 2585, 2586, 1, 0, 0, 0, 2586, 2628, 1, 0, 0, 0, 2587, 2588, 5, 227, 0, 0, 2588, 2589, 5, 105, 0, 0, 2589, 2599, 5, 73, 0, 0, 2590, 2591, 5, 98, 0, 0, 2591, 2592, 5, 264, 0, 0, 2592, 2593, 5, 28, 0, 0, 2593, 2597, 3, 338, 169, 0, 2594, 2595, 5, 86, 0, 0, 2595, 2596, 5, 28, 0, 0, 2596, 2598, 3, 338, 169, 0, 2597, 2594, 1, 0, 0, 0, 2597, 2598, 1, 0, 0, 0, 2598, 2600, 1, 0, 0, 0, 2599, 2590, 1, 0, 0, 0, 2599, 2600, 1, 0, 0, 0, 2600, 2606, 1, 0, 0, 0, 2601, 2602, 5, 42, 0, 0, 2602, 2603, 5, 134, 0, 0, 2603, 2604, 5, 264, 0, 0, 2604, 2605, 5, 28, 0, 0, 2605, 2607, 3, 338, 169, 0, 2606, 2601, 1, 0, 0, 0, 2606, 2607, 1, 0, 0, 0, 2607, 2613, 1, 0, 0, 0, 2608, 2609, 5, 154, 0, 0, 2609, 2610, 5, 136, 0, 0, 2610, 2611, 5, 264, 0, 0, 2611, 2612, 5, 28, 0, 0, 2612, 2614, 3, 338, 169, 0, 2613, 2608, 1, 0, 0, 0, 2613, 2614, 1, 0, 0, 0, 2614, 2619, 1, 0, 0, 0, 2615, 2616, 5, 145, 0, 0, 2616, 2617, 5, 264, 0, 0, 2617, 2618, 5, 28, 0, 0, 2618, 2620, 3, 338, 169, 0, 2619, 2615, 1, 0, 0, 0, 2619, 2620, 1, 0, 0, 0, 2620, 2625, 1, 0, 0, 0, 2621, 2622, 5, 173, 0, 0, 2622, 2623, 5, 71, 0, 0, 2623, 2624, 5, 20, 0, 0, 2624, 2626, 3, 338, 169, 0, 2625, 2621, 1, 0, 0, 0, 2625, 2626, 1, 0, 0, 0, 2626, 2628, 1, 0, 0, 0, 2627, 2578, 1, 0, 0, 0, 2627, 2587, 1, 0, 0, 0, 2628, 211, 1, 0, 0, 0, 2629, 2634, 3, 214, 107, 0, 2630, 2631, 5, 4, 0, 0, 2631, 2633, 3, 214, 107, 0, 2632, 2630, 1, 0, 0, 0, 2633, 2636, 1, 0, 0, 0, 2634, 2632, 1, 0, 0, 0, 2634, 2635, 1, 0, 0, 0, 2635, 213, 1, 0, 0, 0, 2636, 2634, 1, 0, 0, 0, 2637, 2642, 3, 322, 161, 0, 2638, 2639, 5, 5, 0, 0, 2639, 2641, 3, 322, 161, 0, 2640, 2638, 1, 0, 0, 0, 2641, 2644, 1, 0, 0, 0, 2642, 2640, 1, 0, 0, 0, 2642, 2643, 1, 0, 0, 0, 2643, 215, 1, 0, 0, 0, 2644, 2642, 1, 0, 0, 0, 2645, 2650, 3, 218, 109, 0, 2646, 2647, 5, 4, 0, 0, 2647, 2649, 3, 218, 109, 0, 2648, 2646, 1, 0, 0, 0, 2649, 2652, 1, 0, 0, 0, 2650, 2648, 1, 0, 0, 0, 2650, 2651, 1, 0, 0, 0, 2651, 217, 1, 0, 0, 0, 2652, 2650, 1, 0, 0, 0, 2653, 2656, 3, 214, 107, 0, 2654, 2655, 5, 180, 0, 0, 2655, 2657, 3, 62, 31, 0, 2656, 2654, 1, 0, 0, 0, 2656, 2657, 1, 0, 0, 0, 2657, 219, 1, 0, 0, 0, 2658, 2659, 3, 322, 161, 0, 2659, 2660, 5, 5, 0, 0, 2660, 2662, 1, 0, 0, 0, 2661, 2658, 1, 0, 0, 0, 2661, 2662, 1, 0, 0, 0, 2662, 2663, 1, 0, 0, 0, 2663, 2664, 3, 322, 161, 0, 2664, 221, 1, 0, 0, 0, 2665, 2666, 3, 322, 161, 0, 2666, 2667, 5, 5, 0, 0, 2667, 2669, 1, 0, 0, 0, 2668, 2665, 1, 0, 0, 0, 2668, 2669, 1, 0, 0, 0, 2669, 2670, 1, 0, 0, 0, 2670, 2671, 3, 322, 161, 0, 2671, 223, 1, 0, 0, 0, 2672, 2680, 3, 236, 118, 0, 2673, 2675, 5, 20, 0, 0, 2674, 2673, 1, 0, 0, 0, 2674, 2675, 1, 0, 0, 0, 2675, 2678, 1, 0, 0, 0, 2676, 2679, 3, 322, 161, 0, 2677, 2679, 3, 190, 95, 0, 2678, 2676, 1, 0, 0, 0, 2678, 2677, 1, 0, 0, 0, 2679, 2681, 1, 0, 0, 0, 2680, 2674, 1, 0, 0, 0, 2680, 2681, 1, 0, 0, 0, 2681, 225, 1, 0, 0, 0, 2682, 2687, 3, 224, 112, 0, 2683, 2684, 5, 4, 0, 0, 2684, 2686, 3, 224, 112, 0, 2685, 2683, 1, 0, 0, 0, 2686, 2689, 1, 0, 0, 0, 2687, 2685, 1, 0, 0, 0, 2687, 2688, 1, 0, 0, 0, 2688, 227, 1, 0, 0, 0, 2689, 2687, 1, 0, 0, 0, 2690, 2691, 5, 2, 0, 0, 2691, 2696, 3, 230, 115, 0, 2692, 2693, 5, 4, 0, 0, 2693, 2695, 3, 230, 115, 0, 2694, 2692, 1, 0, 0, 0, 2695, 2698, 1, 0, 0, 0, 2696, 2694, 1, 0, 0, 0, 2696, 2697, 1, 0, 0, 0, 2697, 2699, 1, 0, 0, 0, 2698, 2696, 1, 0, 0, 0, 2699, 2700, 5, 3, 0, 0, 2700, 229, 1, 0, 0, 0, 2701, 2704, 3, 232, 116, 0, 2702, 2704, 3, 290, 145, 0, 2703, 2701, 1, 0, 0, 0, 2703, 2702, 1, 0, 0, 0, 2704, 231, 1, 0, 0, 0, 2705, 2719, 3, 320, 160, 0, 2706, 2707, 3, 326, 163, 0, 2707, 2708, 5, 2, 0, 0, 2708, 2713, 3, 234, 117, 0, 2709, 2710, 5, 4, 0, 0, 2710, 2712, 3, 234, 117, 0, 2711, 2709, 1, 0, 0, 0, 2712, 2715, 1, 0, 0, 0, 2713, 2711, 1, 0, 0, 0, 2713, 2714, 1, 0, 0, 0, 2714, 2716, 1, 0, 0, 0, 2715, 2713, 1, 0, 0, 0, 2716, 2717, 5, 3, 0, 0, 2717, 2719, 1, 0, 0, 0, 2718, 2705, 1, 0, 0, 0, 2718, 2706, 1, 0, 0, 0, 2719, 233, 1, 0, 0, 0, 2720, 2723, 3, 320, 160, 0, 2721, 2723, 3, 250, 125, 0, 2722, 2720, 1, 0, 0, 0, 2722, 2721, 1, 0, 0, 0, 2723, 235, 1, 0, 0, 0, 2724, 2725, 3, 240, 120, 0, 2725, 237, 1, 0, 0, 0, 2726, 2731, 3, 236, 118, 0, 2727, 2728, 5, 4, 0, 0, 2728, 2730, 3, 236, 118, 0, 2729, 2727, 1, 0, 0, 0, 2730, 2733, 1, 0, 0, 0, 2731, 2729, 1, 0, 0, 0, 2731, 2732, 1, 0, 0, 0, 2732, 239, 1, 0, 0, 0, 2733, 2731, 1, 0, 0, 0, 2734, 2735, 6, 120, -1, 0, 2735, 2736, 5, 172, 0, 0, 2736, 2747, 3, 240, 120, 5, 2737, 2738, 5, 90, 0, 0, 2738, 2739, 5, 2, 0, 0, 2739, 2740, 3, 36, 18, 0, 2740, 2741, 5, 3, 0, 0, 2741, 2747, 1, 0, 0, 0, 2742, 2744, 3, 244, 122, 0, 2743, 2745, 3, 242, 121, 0, 2744, 2743, 1, 0, 0, 0, 2744, 2745, 1, 0, 0, 0, 2745, 2747, 1, 0, 0, 0, 2746, 2734, 1, 0, 0, 0, 2746, 2737, 1, 0, 0, 0, 2746, 2742, 1, 0, 0, 0, 2747, 2756, 1, 0, 0, 0, 2748, 2749, 10, 2, 0, 0, 2749, 2750, 5, 14, 0, 0, 2750, 2755, 3, 240, 120, 3, 2751, 2752, 10, 1, 0, 0, 2752, 2753, 5, 181, 0, 0, 2753, 2755, 3, 240, 120, 2, 2754, 2748, 1, 0, 0, 0, 2754, 2751, 1, 0, 0, 0, 2755, 2758, 1, 0, 0, 0, 2756, 2754, 1, 0, 0, 0, 2756, 2757, 1, 0, 0, 0, 2757, 241, 1, 0, 0, 0, 2758, 2756, 1, 0, 0, 0, 2759, 2761, 5, 172, 0, 0, 2760, 2759, 1, 0, 0, 0, 2760, 2761, 1, 0, 0, 0, 2761, 2762, 1, 0, 0, 0, 2762, 2763, 5, 24, 0, 0, 2763, 2764, 3, 244, 122, 0, 2764, 2765, 5, 14, 0, 0, 2765, 2766, 3, 244, 122, 0, 2766, 2842, 1, 0, 0, 0, 2767, 2769, 5, 172, 0, 0, 2768, 2767, 1, 0, 0, 0, 2768, 2769, 1, 0, 0, 0, 2769, 2770, 1, 0, 0, 0, 2770, 2771, 5, 122, 0, 0, 2771, 2772, 5, 2, 0, 0, 2772, 2777, 3, 236, 118, 0, 2773, 2774, 5, 4, 0, 0, 2774, 2776, 3, 236, 118, 0, 2775, 2773, 1, 0, 0, 0, 2776, 2779, 1, 0, 0, 0, 2777, 2775, 1, 0, 0, 0, 2777, 2778, 1, 0, 0, 0, 2778, 2780, 1, 0, 0, 0, 2779, 2777, 1, 0, 0, 0, 2780, 2781, 5, 3, 0, 0, 2781, 2842, 1, 0, 0, 0, 2782, 2784, 5, 172, 0, 0, 2783, 2782, 1, 0, 0, 0, 2783, 2784, 1, 0, 0, 0, 2784, 2785, 1, 0, 0, 0, 2785, 2786, 5, 122, 0, 0, 2786, 2787, 5, 2, 0, 0, 2787, 2788, 3, 36, 18, 0, 2788, 2789, 5, 3, 0, 0, 2789, 2842, 1, 0, 0, 0, 2790, 2792, 5, 172, 0, 0, 2791, 2790, 1, 0, 0, 0, 2791, 2792, 1, 0, 0, 0, 2792, 2793, 1, 0, 0, 0, 2793, 2794, 5, 222, 0, 0, 2794, 2842, 3, 244, 122, 0, 2795, 2797, 5, 172, 0, 0, 2796, 2795, 1, 0, 0, 0, 2796, 2797, 1, 0, 0, 0, 2797, 2798, 1, 0, 0, 0, 2798, 2799, 7, 22, 0, 0, 2799, 2813, 7, 23, 0, 0, 2800, 2801, 5, 2, 0, 0, 2801, 2814, 5, 3, 0, 0, 2802, 2803, 5, 2, 0, 0, 2803, 2808, 3, 236, 118, 0, 2804, 2805, 5, 4, 0, 0, 2805, 2807, 3, 236, 118, 0, 2806, 2804, 1, 0, 0, 0, 2807, 2810, 1, 0, 0, 0, 2808, 2806, 1, 0, 0, 0, 2808, 2809, 1, 0, 0, 0, 2809, 2811, 1, 0, 0, 0, 2810, 2808, 1, 0, 0, 0, 2811, 2812, 5, 3, 0, 0, 2812, 2814, 1, 0, 0, 0, 2813, 2800, 1, 0, 0, 0, 2813, 2802, 1, 0, 0, 0, 2814, 2842, 1, 0, 0, 0, 2815, 2817, 5, 172, 0, 0, 2816, 2815, 1, 0, 0, 0, 2816, 2817, 1, 0, 0, 0, 2817, 2818, 1, 0, 0, 0, 2818, 2819, 7, 22, 0, 0, 2819, 2822, 3, 244, 122, 0, 2820, 2821, 5, 85, 0, 0, 2821, 2823, 3, 338, 169, 0, 2822, 2820, 1, 0, 0, 0, 2822, 2823, 1, 0, 0, 0, 2823, 2842, 1, 0, 0, 0, 2824, 2826, 5, 133, 0, 0, 2825, 2827, 5, 172, 0, 0, 2826, 2825, 1, 0, 0, 0, 2826, 2827, 1, 0, 0, 0, 2827, 2828, 1, 0, 0, 0, 2828, 2842, 5, 173, 0, 0, 2829, 2831, 5, 133, 0, 0, 2830, 2832, 5, 172, 0, 0, 2831, 2830, 1, 0, 0, 0, 2831, 2832, 1, 0, 0, 0, 2832, 2833, 1, 0, 0, 0, 2833, 2842, 7, 24, 0, 0, 2834, 2836, 5, 133, 0, 0, 2835, 2837, 5, 172, 0, 0, 2836, 2835, 1, 0, 0, 0, 2836, 2837, 1, 0, 0, 0, 2837, 2838, 1, 0, 0, 0, 2838, 2839, 5, 79, 0, 0, 2839, 2840, 5, 107, 0, 0, 2840, 2842, 3, 244, 122, 0, 2841, 2760, 1, 0, 0, 0, 2841, 2768, 1, 0, 0, 0, 2841, 2783, 1, 0, 0, 0, 2841, 2791, 1, 0, 0, 0, 2841, 2796, 1, 0, 0, 0, 2841, 2816, 1, 0, 0, 0, 2841, 2824, 1, 0, 0, 0, 2841, 2829, 1, 0, 0, 0, 2841, 2834, 1, 0, 0, 0, 2842, 243, 1, 0, 0, 0, 2843, 2844, 6, 122, -1, 0, 2844, 2848, 3, 248, 124, 0, 2845, 2846, 7, 25, 0, 0, 2846, 2848, 3, 244, 122, 7, 2847, 2843, 1, 0, 0, 0, 2847, 2845, 1, 0, 0, 0, 2848, 2870, 1, 0, 0, 0, 2849, 2850, 10, 6, 0, 0, 2850, 2851, 7, 26, 0, 0, 2851, 2869, 3, 244, 122, 7, 2852, 2853, 10, 5, 0, 0, 2853, 2854, 7, 27, 0, 0, 2854, 2869, 3, 244, 122, 6, 2855, 2856, 10, 4, 0, 0, 2856, 2857, 5, 322, 0, 0, 2857, 2869, 3, 244, 122, 5, 2858, 2859, 10, 3, 0, 0, 2859, 2860, 5, 325, 0, 0, 2860, 2869, 3, 244, 122, 4, 2861, 2862, 10, 2, 0, 0, 2862, 2863, 5, 323, 0, 0, 2863, 2869, 3, 244, 122, 3, 2864, 2865, 10, 1, 0, 0, 2865, 2866, 3, 252, 126, 0, 2866, 2867, 3, 244, 122, 2, 2867, 2869, 1, 0, 0, 0, 2868, 2849, 1, 0, 0, 0, 2868, 2852, 1, 0, 0, 0, 2868, 2855, 1, 0, 0, 0, 2868, 2858, 1, 0, 0, 0, 2868, 2861, 1, 0, 0, 0, 2868, 2864, 1, 0, 0, 0, 2869, 2872, 1, 0, 0, 0, 2870, 2868, 1, 0, 0, 0, 2870, 2871, 1, 0, 0, 0, 2871, 245, 1, 0, 0, 0, 2872, 2870, 1, 0, 0, 0, 2873, 2874, 7, 28, 0, 0, 2874, 247, 1, 0, 0, 0, 2875, 2876, 6, 124, -1, 0, 2876, 3114, 7, 29, 0, 0, 2877, 2878, 7, 30, 0, 0, 2878, 2879, 5, 2, 0, 0, 2879, 2880, 3, 246, 123, 0, 2880, 2881, 5, 4, 0, 0, 2881, 2882, 3, 244, 122, 0, 2882, 2883, 5, 4, 0, 0, 2883, 2884, 3, 244, 122, 0, 2884, 2885, 5, 3, 0, 0, 2885, 3114, 1, 0, 0, 0, 2886, 2887, 7, 31, 0, 0, 2887, 2888, 5, 2, 0, 0, 2888, 2889, 3, 246, 123, 0, 2889, 2890, 5, 4, 0, 0, 2890, 2891, 3, 244, 122, 0, 2891, 2892, 5, 4, 0, 0, 2892, 2893, 3, 244, 122, 0, 2893, 2894, 5, 3, 0, 0, 2894, 3114, 1, 0, 0, 0, 2895, 2897, 5, 31, 0, 0, 2896, 2898, 3, 304, 152, 0, 2897, 2896, 1, 0, 0, 0, 2898, 2899, 1, 0, 0, 0, 2899, 2897, 1, 0, 0, 0, 2899, 2900, 1, 0, 0, 0, 2900, 2903, 1, 0, 0, 0, 2901, 2902, 5, 83, 0, 0, 2902, 2904, 3, 236, 118, 0, 2903, 2901, 1, 0, 0, 0, 2903, 2904, 1, 0, 0, 0, 2904, 2905, 1, 0, 0, 0, 2905, 2906, 5, 84, 0, 0, 2906, 3114, 1, 0, 0, 0, 2907, 2908, 5, 31, 0, 0, 2908, 2910, 3, 236, 118, 0, 2909, 2911, 3, 304, 152, 0, 2910, 2909, 1, 0, 0, 0, 2911, 2912, 1, 0, 0, 0, 2912, 2910, 1, 0, 0, 0, 2912, 2913, 1, 0, 0, 0, 2913, 2916, 1, 0, 0, 0, 2914, 2915, 5, 83, 0, 0, 2915, 2917, 3, 236, 118, 0, 2916, 2914, 1, 0, 0, 0, 2916, 2917, 1, 0, 0, 0, 2917, 2918, 1, 0, 0, 0, 2918, 2919, 5, 84, 0, 0, 2919, 3114, 1, 0, 0, 0, 2920, 2921, 7, 32, 0, 0, 2921, 2922, 5, 2, 0, 0, 2922, 2923, 3, 236, 118, 0, 2923, 2924, 5, 20, 0, 0, 2924, 2925, 3, 278, 139, 0, 2925, 2926, 5, 3, 0, 0, 2926, 3114, 1, 0, 0, 0, 2927, 2928, 5, 252, 0, 0, 2928, 2937, 5, 2, 0, 0, 2929, 2934, 3, 224, 112, 0, 2930, 2931, 5, 4, 0, 0, 2931, 2933, 3, 224, 112, 0, 2932, 2930, 1, 0, 0, 0, 2933, 2936, 1, 0, 0, 0, 2934, 2932, 1, 0, 0, 0, 2934, 2935, 1, 0, 0, 0, 2935, 2938, 1, 0, 0, 0, 2936, 2934, 1, 0, 0, 0, 2937, 2929, 1, 0, 0, 0, 2937, 2938, 1, 0, 0, 0, 2938, 2939, 1, 0, 0, 0, 2939, 3114, 5, 3, 0, 0, 2940, 2941, 5, 101, 0, 0, 2941, 2942, 5, 2, 0, 0, 2942, 2945, 3, 236, 118, 0, 2943, 2944, 5, 120, 0, 0, 2944, 2946, 5, 174, 0, 0, 2945, 2943, 1, 0, 0, 0, 2945, 2946, 1, 0, 0, 0, 2946, 2947, 1, 0, 0, 0, 2947, 2948, 5, 3, 0, 0, 2948, 3114, 1, 0, 0, 0, 2949, 2950, 5, 17, 0, 0, 2950, 2951, 5, 2, 0, 0, 2951, 2954, 3, 236, 118, 0, 2952, 2953, 5, 120, 0, 0, 2953, 2955, 5, 174, 0, 0, 2954, 2952, 1, 0, 0, 0, 2954, 2955, 1, 0, 0, 0, 2955, 2956, 1, 0, 0, 0, 2956, 2957, 5, 3, 0, 0, 2957, 3114, 1, 0, 0, 0, 2958, 2959, 5, 137, 0, 0, 2959, 2960, 5, 2, 0, 0, 2960, 2963, 3, 236, 118, 0, 2961, 2962, 5, 120, 0, 0, 2962, 2964, 5, 174, 0, 0, 2963, 2961, 1, 0, 0, 0, 2963, 2964, 1, 0, 0, 0, 2964, 2965, 1, 0, 0, 0, 2965, 2966, 5, 3, 0, 0, 2966, 3114, 1, 0, 0, 0, 2967, 2968, 5, 198, 0, 0, 2968, 2969, 5, 2, 0, 0, 2969, 2970, 3, 244, 122, 0, 2970, 2971, 5, 122, 0, 0, 2971, 2972, 3, 244, 122, 0, 2972, 2973, 5, 3, 0, 0, 2973, 3114, 1, 0, 0, 0, 2974, 3114, 3, 250, 125, 0, 2975, 3114, 5, 318, 0, 0, 2976, 2977, 3, 320, 160, 0, 2977, 2978, 5, 5, 0, 0, 2978, 2979, 5, 318, 0, 0, 2979, 3114, 1, 0, 0, 0, 2980, 2981, 5, 2, 0, 0, 2981, 2984, 3, 224, 112, 0, 2982, 2983, 5, 4, 0, 0, 2983, 2985, 3, 224, 112, 0, 2984, 2982, 1, 0, 0, 0, 2985, 2986, 1, 0, 0, 0, 2986, 2984, 1, 0, 0, 0, 2986, 2987, 1, 0, 0, 0, 2987, 2988, 1, 0, 0, 0, 2988, 2989, 5, 3, 0, 0, 2989, 3114, 1, 0, 0, 0, 2990, 2991, 5, 2, 0, 0, 2991, 2992, 3, 36, 18, 0, 2992, 2993, 5, 3, 0, 0, 2993, 3114, 1, 0, 0, 0, 2994, 2995, 3, 318, 159, 0, 2995, 3007, 5, 2, 0, 0, 2996, 2998, 3, 174, 87, 0, 2997, 2996, 1, 0, 0, 0, 2997, 2998, 1, 0, 0, 0, 2998, 2999, 1, 0, 0, 0, 2999, 3004, 3, 236, 118, 0, 3000, 3001, 5, 4, 0, 0, 3001, 3003, 3, 236, 118, 0, 3002, 3000, 1, 0, 0, 0, 3003, 3006, 1, 0, 0, 0, 3004, 3002, 1, 0, 0, 0, 3004, 3005, 1, 0, 0, 0, 3005, 3008, 1, 0, 0, 0, 3006, 3004, 1, 0, 0, 0, 3007, 2997, 1, 0, 0, 0, 3007, 3008, 1, 0, 0, 0, 3008, 3009, 1, 0, 0, 0, 3009, 3016, 5, 3, 0, 0, 3010, 3011, 5, 99, 0, 0, 3011, 3012, 5, 2, 0, 0, 3012, 3013, 5, 301, 0, 0, 3013, 3014, 3, 240, 120, 0, 3014, 3015, 5, 3, 0, 0, 3015, 3017, 1, 0, 0, 0, 3016, 3010, 1, 0, 0, 0, 3016, 3017, 1, 0, 0, 0, 3017, 3020, 1, 0, 0, 0, 3018, 3019, 7, 33, 0, 0, 3019, 3021, 5, 174, 0, 0, 3020, 3018, 1, 0, 0, 0, 3020, 3021, 1, 0, 0, 0, 3021, 3024, 1, 0, 0, 0, 3022, 3023, 5, 186, 0, 0, 3023, 3025, 3, 310, 155, 0, 3024, 3022, 1, 0, 0, 0, 3024, 3025, 1, 0, 0, 0, 3025, 3114, 1, 0, 0, 0, 3026, 3027, 3, 326, 163, 0, 3027, 3028, 5, 327, 0, 0, 3028, 3029, 3, 236, 118, 0, 3029, 3114, 1, 0, 0, 0, 3030, 3031, 5, 2, 0, 0, 3031, 3034, 3, 326, 163, 0, 3032, 3033, 5, 4, 0, 0, 3033, 3035, 3, 326, 163, 0, 3034, 3032, 1, 0, 0, 0, 3035, 3036, 1, 0, 0, 0, 3036, 3034, 1, 0, 0, 0, 3036, 3037, 1, 0, 0, 0, 3037, 3038, 1, 0, 0, 0, 3038, 3039, 5, 3, 0, 0, 3039, 3040, 5, 327, 0, 0, 3040, 3041, 3, 236, 118, 0, 3041, 3114, 1, 0, 0, 0, 3042, 3114, 3, 326, 163, 0, 3043, 3044, 5, 2, 0, 0, 3044, 3045, 3, 236, 118, 0, 3045, 3046, 5, 3, 0, 0, 3046, 3114, 1, 0, 0, 0, 3047, 3048, 5, 95, 0, 0, 3048, 3049, 5, 2, 0, 0, 3049, 3050, 3, 326, 163, 0, 3050, 3051, 5, 107, 0, 0, 3051, 3052, 3, 244, 122, 0, 3052, 3053, 5, 3, 0, 0, 3053, 3114, 1, 0, 0, 0, 3054, 3055, 7, 34, 0, 0, 3055, 3056, 5, 2, 0, 0, 3056, 3057, 3, 244, 122, 0, 3057, 3058, 7, 35, 0, 0, 3058, 3061, 3, 244, 122, 0, 3059, 3060, 7, 36, 0, 0, 3060, 3062, 3, 244, 122, 0, 3061, 3059, 1, 0, 0, 0, 3061, 3062, 1, 0, 0, 0, 3062, 3063, 1, 0, 0, 0, 3063, 3064, 5, 3, 0, 0, 3064, 3114, 1, 0, 0, 0, 3065, 3066, 5, 276, 0, 0, 3066, 3068, 5, 2, 0, 0, 3067, 3069, 7, 37, 0, 0, 3068, 3067, 1, 0, 0, 0, 3068, 3069, 1, 0, 0, 0, 3069, 3071, 1, 0, 0, 0, 3070, 3072, 3, 244, 122, 0, 3071, 3070, 1, 0, 0, 0, 3071, 3072, 1, 0, 0, 0, 3072, 3073, 1, 0, 0, 0, 3073, 3074, 5, 107, 0, 0, 3074, 3075, 3, 244, 122, 0, 3075, 3076, 5, 3, 0, 0, 3076, 3114, 1, 0, 0, 0, 3077, 3078, 5, 188, 0, 0, 3078, 3079, 5, 2, 0, 0, 3079, 3080, 3, 244, 122, 0, 3080, 3081, 5, 197, 0, 0, 3081, 3082, 3, 244, 122, 0, 3082, 3083, 5, 107, 0, 0, 3083, 3086, 3, 244, 122, 0, 3084, 3085, 5, 103, 0, 0, 3085, 3087, 3, 244, 122, 0, 3086, 3084, 1, 0, 0, 0, 3086, 3087, 1, 0, 0, 0, 3087, 3088, 1, 0, 0, 0, 3088, 3089, 5, 3, 0, 0, 3089, 3114, 1, 0, 0, 0, 3090, 3091, 7, 38, 0, 0, 3091, 3092, 5, 2, 0, 0, 3092, 3093, 3, 244, 122, 0, 3093, 3094, 5, 3, 0, 0, 3094, 3095, 5, 304, 0, 0, 3095, 3096, 5, 114, 0, 0, 3096, 3097, 5, 2, 0, 0, 3097, 3098, 5, 182, 0, 0, 3098, 3099, 5, 28, 0, 0, 3099, 3100, 3, 92, 46, 0, 3100, 3107, 5, 3, 0, 0, 3101, 3102, 5, 99, 0, 0, 3102, 3103, 5, 2, 0, 0, 3103, 3104, 5, 301, 0, 0, 3104, 3105, 3, 240, 120, 0, 3105, 3106, 5, 3, 0, 0, 3106, 3108, 1, 0, 0, 0, 3107, 3101, 1, 0, 0, 0, 3107, 3108, 1, 0, 0, 0, 3108, 3111, 1, 0, 0, 0, 3109, 3110, 5, 186, 0, 0, 3110, 3112, 3, 310, 155, 0, 3111, 3109, 1, 0, 0, 0, 3111, 3112, 1, 0, 0, 0, 3112, 3114, 1, 0, 0, 0, 3113, 2875, 1, 0, 0, 0, 3113, 2877, 1, 0, 0, 0, 3113, 2886, 1, 0, 0, 0, 3113, 2895, 1, 0, 0, 0, 3113, 2907, 1, 0, 0, 0, 3113, 2920, 1, 0, 0, 0, 3113, 2927, 1, 0, 0, 0, 3113, 2940, 1, 0, 0, 0, 3113, 2949, 1, 0, 0, 0, 3113, 2958, 1, 0, 0, 0, 3113, 2967, 1, 0, 0, 0, 3113, 2974, 1, 0, 0, 0, 3113, 2975, 1, 0, 0, 0, 3113, 2976, 1, 0, 0, 0, 3113, 2980, 1, 0, 0, 0, 3113, 2990, 1, 0, 0, 0, 3113, 2994, 1, 0, 0, 0, 3113, 3026, 1, 0, 0, 0, 3113, 3030, 1, 0, 0, 0, 3113, 3042, 1, 0, 0, 0, 3113, 3043, 1, 0, 0, 0, 3113, 3047, 1, 0, 0, 0, 3113, 3054, 1, 0, 0, 0, 3113, 3065, 1, 0, 0, 0, 3113, 3077, 1, 0, 0, 0, 3113, 3090, 1, 0, 0, 0, 3114, 3125, 1, 0, 0, 0, 3115, 3116, 10, 9, 0, 0, 3116, 3117, 5, 6, 0, 0, 3117, 3118, 3, 244, 122, 0, 3118, 3119, 5, 7, 0, 0, 3119, 3124, 1, 0, 0, 0, 3120, 3121, 10, 7, 0, 0, 3121, 3122, 5, 5, 0, 0, 3122, 3124, 3, 326, 163, 0, 3123, 3115, 1, 0, 0, 0, 3123, 3120, 1, 0, 0, 0, 3124, 3127, 1, 0, 0, 0, 3125, 3123, 1, 0, 0, 0, 3125, 3126, 1, 0, 0, 0, 3126, 249, 1, 0, 0, 0, 3127, 3125, 1, 0, 0, 0, 3128, 3143, 5, 173, 0, 0, 3129, 3130, 5, 326, 0, 0, 3130, 3143, 3, 326, 163, 0, 3131, 3143, 3, 260, 130, 0, 3132, 3133, 3, 326, 163, 0, 3133, 3134, 3, 338, 169, 0, 3134, 3143, 1, 0, 0, 0, 3135, 3143, 3, 334, 167, 0, 3136, 3143, 3, 258, 129, 0, 3137, 3139, 3, 338, 169, 0, 3138, 3137, 1, 0, 0, 0, 3139, 3140, 1, 0, 0, 0, 3140, 3138, 1, 0, 0, 0, 3140, 3141, 1, 0, 0, 0, 3141, 3143, 1, 0, 0, 0, 3142, 3128, 1, 0, 0, 0, 3142, 3129, 1, 0, 0, 0, 3142, 3131, 1, 0, 0, 0, 3142, 3132, 1, 0, 0, 0, 3142, 3135, 1, 0, 0, 0, 3142, 3136, 1, 0, 0, 0, 3142, 3138, 1, 0, 0, 0, 3143, 251, 1, 0, 0, 0, 3144, 3145, 7, 39, 0, 0, 3145, 253, 1, 0, 0, 0, 3146, 3147, 7, 40, 0, 0, 3147, 255, 1, 0, 0, 0, 3148, 3149, 7, 41, 0, 0, 3149, 257, 1, 0, 0, 0, 3150, 3151, 7, 42, 0, 0, 3151, 259, 1, 0, 0, 0, 3152, 3155, 5, 131, 0, 0, 3153, 3156, 3, 262, 131, 0, 3154, 3156, 3, 266, 133, 0, 3155, 3153, 1, 0, 0, 0, 3155, 3154, 1, 0, 0, 0, 3156, 261, 1, 0, 0, 0, 3157, 3159, 3, 264, 132, 0, 3158, 3160, 3, 268, 134, 0, 3159, 3158, 1, 0, 0, 0, 3159, 3160, 1, 0, 0, 0, 3160, 263, 1, 0, 0, 0, 3161, 3162, 3, 270, 135, 0, 3162, 3163, 3, 272, 136, 0, 3163, 3165, 1, 0, 0, 0, 3164, 3161, 1, 0, 0, 0, 3165, 3166, 1, 0, 0, 0, 3166, 3164, 1, 0, 0, 0, 3166, 3167, 1, 0, 0, 0, 3167, 265, 1, 0, 0, 0, 3168, 3171, 3, 268, 134, 0, 3169, 3172, 3, 264, 132, 0, 3170, 3172, 3, 268, 134, 0, 3171, 3169, 1, 0, 0, 0, 3171, 3170, 1, 0, 0, 0, 3171, 3172, 1, 0, 0, 0, 3172, 267, 1, 0, 0, 0, 3173, 3174, 3, 270, 135, 0, 3174, 3175, 3, 274, 137, 0, 3175, 3176, 5, 270, 0, 0, 3176, 3177, 3, 274, 137, 0, 3177, 269, 1, 0, 0, 0, 3178, 3180, 7, 43, 0, 0, 3179, 3178, 1, 0, 0, 0, 3179, 3180, 1, 0, 0, 0, 3180, 3184, 1, 0, 0, 0, 3181, 3185, 5, 335, 0, 0, 3182, 3185, 5, 337, 0, 0, 3183, 3185, 3, 338, 169, 0, 3184, 3181, 1, 0, 0, 0, 3184, 3182, 1, 0, 0, 0, 3184, 3183, 1, 0, 0, 0, 3185, 271, 1, 0, 0, 0, 3186, 3187, 7, 44, 0, 0, 3187, 273, 1, 0, 0, 0, 3188, 3189, 7, 45, 0, 0, 3189, 275, 1, 0, 0, 0, 3190, 3194, 5, 101, 0, 0, 3191, 3192, 5, 9, 0, 0, 3192, 3194, 3, 322, 161, 0, 3193, 3190, 1, 0, 0, 0, 3193, 3191, 1, 0, 0, 0, 3194, 277, 1, 0, 0, 0, 3195, 3196, 5, 19, 0, 0, 3196, 3197, 5, 312, 0, 0, 3197, 3198, 3, 278, 139, 0, 3198, 3199, 5, 314, 0, 0, 3199, 3242, 1, 0, 0, 0, 3200, 3201, 5, 154, 0, 0, 3201, 3202, 5, 312, 0, 0, 3202, 3203, 3, 278, 139, 0, 3203, 3204, 5, 4, 0, 0, 3204, 3205, 3, 278, 139, 0, 3205, 3206, 5, 314, 0, 0, 3206, 3242, 1, 0, 0, 0, 3207, 3214, 5, 252, 0, 0, 3208, 3210, 5, 312, 0, 0, 3209, 3211, 3, 300, 150, 0, 3210, 3209, 1, 0, 0, 0, 3210, 3211, 1, 0, 0, 0, 3211, 3212, 1, 0, 0, 0, 3212, 3215, 5, 314, 0, 0, 3213, 3215, 5, 310, 0, 0, 3214, 3208, 1, 0, 0, 0, 3214, 3213, 1, 0, 0, 0, 3215, 3242, 1, 0, 0, 0, 3216, 3217, 5, 131, 0, 0, 3217, 3220, 7, 46, 0, 0, 3218, 3219, 5, 270, 0, 0, 3219, 3221, 5, 163, 0, 0, 3220, 3218, 1, 0, 0, 0, 3220, 3221, 1, 0, 0, 0, 3221, 3242, 1, 0, 0, 0, 3222, 3223, 5, 131, 0, 0, 3223, 3226, 7, 47, 0, 0, 3224, 3225, 5, 270, 0, 0, 3225, 3227, 7, 48, 0, 0, 3226, 3224, 1, 0, 0, 0, 3226, 3227, 1, 0, 0, 0, 3227, 3242, 1, 0, 0, 0, 3228, 3239, 3, 326, 163, 0, 3229, 3230, 5, 2, 0, 0, 3230, 3235, 5, 335, 0, 0, 3231, 3232, 5, 4, 0, 0, 3232, 3234, 5, 335, 0, 0, 3233, 3231, 1, 0, 0, 0, 3234, 3237, 1, 0, 0, 0, 3235, 3233, 1, 0, 0, 0, 3235, 3236, 1, 0, 0, 0, 3236, 3238, 1, 0, 0, 0, 3237, 3235, 1, 0, 0, 0, 3238, 3240, 5, 3, 0, 0, 3239, 3229, 1, 0, 0, 0, 3239, 3240, 1, 0, 0, 0, 3240, 3242, 1, 0, 0, 0, 3241, 3195, 1, 0, 0, 0, 3241, 3200, 1, 0, 0, 0, 3241, 3207, 1, 0, 0, 0, 3241, 3216, 1, 0, 0, 0, 3241, 3222, 1, 0, 0, 0, 3241, 3228, 1, 0, 0, 0, 3242, 279, 1, 0, 0, 0, 3243, 3248, 3, 282, 141, 0, 3244, 3245, 5, 4, 0, 0, 3245, 3247, 3, 282, 141, 0, 3246, 3244, 1, 0, 0, 0, 3247, 3250, 1, 0, 0, 0, 3248, 3246, 1, 0, 0, 0, 3248, 3249, 1, 0, 0, 0, 3249, 281, 1, 0, 0, 0, 3250, 3248, 1, 0, 0, 0, 3251, 3252, 3, 214, 107, 0, 3252, 3256, 3, 278, 139, 0, 3253, 3255, 3, 284, 142, 0, 3254, 3253, 1, 0, 0, 0, 3255, 3258, 1, 0, 0, 0, 3256, 3254, 1, 0, 0, 0, 3256, 3257, 1, 0, 0, 0, 3257, 283, 1, 0, 0, 0, 3258, 3256, 1, 0, 0, 0, 3259, 3260, 5, 172, 0, 0, 3260, 3265, 5, 173, 0, 0, 3261, 3265, 3, 286, 143, 0, 3262, 3265, 3, 34, 17, 0, 3263, 3265, 3, 276, 138, 0, 3264, 3259, 1, 0, 0, 0, 3264, 3261, 1, 0, 0, 0, 3264, 3262, 1, 0, 0, 0, 3264, 3263, 1, 0, 0, 0, 3265, 285, 1, 0, 0, 0, 3266, 3267, 5, 70, 0, 0, 3267, 3268, 3, 236, 118, 0, 3268, 287, 1, 0, 0, 0, 3269, 3274, 3, 290, 145, 0, 3270, 3271, 5, 4, 0, 0, 3271, 3273, 3, 290, 145, 0, 3272, 3270, 1, 0, 0, 0, 3273, 3276, 1, 0, 0, 0, 3274, 3272, 1, 0, 0, 0, 3274, 3275, 1, 0, 0, 0, 3275, 289, 1, 0, 0, 0, 3276, 3274, 1, 0, 0, 0, 3277, 3278, 3, 322, 161, 0, 3278, 3281, 3, 278, 139, 0, 3279, 3280, 5, 172, 0, 0, 3280, 3282, 5, 173, 0, 0, 3281, 3279, 1, 0, 0, 0, 3281, 3282, 1, 0, 0, 0, 3282, 3284, 1, 0, 0, 0, 3283, 3285, 3, 34, 17, 0, 3284, 3283, 1, 0, 0, 0, 3284, 3285, 1, 0, 0, 0, 3285, 291, 1, 0, 0, 0, 3286, 3291, 3, 294, 147, 0, 3287, 3288, 5, 4, 0, 0, 3288, 3290, 3, 294, 147, 0, 3289, 3287, 1, 0, 0, 0, 3290, 3293, 1, 0, 0, 0, 3291, 3289, 1, 0, 0, 0, 3291, 3292, 1, 0, 0, 0, 3292, 293, 1, 0, 0, 0, 3293, 3291, 1, 0, 0, 0, 3294, 3295, 3, 322, 161, 0, 3295, 3299, 3, 278, 139, 0, 3296, 3298, 3, 296, 148, 0, 3297, 3296, 1, 0, 0, 0, 3298, 3301, 1, 0, 0, 0, 3299, 3297, 1, 0, 0, 0, 3299, 3300, 1, 0, 0, 0, 3300, 295, 1, 0, 0, 0, 3301, 3299, 1, 0, 0, 0, 3302, 3303, 5, 172, 0, 0, 3303, 3308, 5, 173, 0, 0, 3304, 3308, 3, 286, 143, 0, 3305, 3308, 3, 298, 149, 0, 3306, 3308, 3, 34, 17, 0, 3307, 3302, 1, 0, 0, 0, 3307, 3304, 1, 0, 0, 0, 3307, 3305, 1, 0, 0, 0, 3307, 3306, 1, 0, 0, 0, 3308, 297, 1, 0, 0, 0, 3309, 3310, 5, 111, 0, 0, 3310, 3311, 5, 12, 0, 0, 3311, 3312, 5, 20, 0, 0, 3312, 3313, 5, 2, 0, 0, 3313, 3314, 3, 236, 118, 0, 3314, 3315, 5, 3, 0, 0, 3315, 299, 1, 0, 0, 0, 3316, 3321, 3, 302, 151, 0, 3317, 3318, 5, 4, 0, 0, 3318, 3320, 3, 302, 151, 0, 3319, 3317, 1, 0, 0, 0, 3320, 3323, 1, 0, 0, 0, 3321, 3319, 1, 0, 0, 0, 3321, 3322, 1, 0, 0, 0, 3322, 301, 1, 0, 0, 0, 3323, 3321, 1, 0, 0, 0, 3324, 3326, 3, 326, 163, 0, 3325, 3327, 5, 326, 0, 0, 3326, 3325, 1, 0, 0, 0, 3326, 3327, 1, 0, 0, 0, 3327, 3328, 1, 0, 0, 0, 3328, 3331, 3, 278, 139, 0, 3329, 3330, 5, 172, 0, 0, 3330, 3332, 5, 173, 0, 0, 3331, 3329, 1, 0, 0, 0, 3331, 3332, 1, 0, 0, 0, 3332, 3334, 1, 0, 0, 0, 3333, 3335, 3, 34, 17, 0, 3334, 3333, 1, 0, 0, 0, 3334, 3335, 1, 0, 0, 0, 3335, 303, 1, 0, 0, 0, 3336, 3337, 5, 300, 0, 0, 3337, 3338, 3, 236, 118, 0, 3338, 3339, 5, 265, 0, 0, 3339, 3340, 3, 236, 118, 0, 3340, 305, 1, 0, 0, 0, 3341, 3342, 5, 302, 0, 0, 3342, 3347, 3, 308, 154, 0, 3343, 3344, 5, 4, 0, 0, 3344, 3346, 3, 308, 154, 0, 3345, 3343, 1, 0, 0, 0, 3346, 3349, 1, 0, 0, 0, 3347, 3345, 1, 0, 0, 0, 3347, 3348, 1, 0, 0, 0, 3348, 307, 1, 0, 0, 0, 3349, 3347, 1, 0, 0, 0, 3350, 3351, 3, 322, 161, 0, 3351, 3352, 5, 20, 0, 0, 3352, 3353, 3, 310, 155, 0, 3353, 309, 1, 0, 0, 0, 3354, 3401, 3, 322, 161, 0, 3355, 3356, 5, 2, 0, 0, 3356, 3357, 3, 322, 161, 0, 3357, 3358, 5, 3, 0, 0, 3358, 3401, 1, 0, 0, 0, 3359, 3394, 5, 2, 0, 0, 3360, 3361, 5, 38, 0, 0, 3361, 3362, 5, 28, 0, 0, 3362, 3367, 3, 236, 118, 0, 3363, 3364, 5, 4, 0, 0, 3364, 3366, 3, 236, 118, 0, 3365, 3363, 1, 0, 0, 0, 3366, 3369, 1, 0, 0, 0, 3367, 3365, 1, 0, 0, 0, 3367, 3368, 1, 0, 0, 0, 3368, 3395, 1, 0, 0, 0, 3369, 3367, 1, 0, 0, 0, 3370, 3371, 7, 49, 0, 0, 3371, 3372, 5, 28, 0, 0, 3372, 3377, 3, 236, 118, 0, 3373, 3374, 5, 4, 0, 0, 3374, 3376, 3, 236, 118, 0, 3375, 3373, 1, 0, 0, 0, 3376, 3379, 1, 0, 0, 0, 3377, 3375, 1, 0, 0, 0, 3377, 3378, 1, 0, 0, 0, 3378, 3381, 1, 0, 0, 0, 3379, 3377, 1, 0, 0, 0, 3380, 3370, 1, 0, 0, 0, 3380, 3381, 1, 0, 0, 0, 3381, 3392, 1, 0, 0, 0, 3382, 3383, 7, 50, 0, 0, 3383, 3384, 5, 28, 0, 0, 3384, 3389, 3, 92, 46, 0, 3385, 3386, 5, 4, 0, 0, 3386, 3388, 3, 92, 46, 0, 3387, 3385, 1, 0, 0, 0, 3388, 3391, 1, 0, 0, 0, 3389, 3387, 1, 0, 0, 0, 3389, 3390, 1, 0, 0, 0, 3390, 3393, 1, 0, 0, 0, 3391, 3389, 1, 0, 0, 0, 3392, 3382, 1, 0, 0, 0, 3392, 3393, 1, 0, 0, 0, 3393, 3395, 1, 0, 0, 0, 3394, 3360, 1, 0, 0, 0, 3394, 3380, 1, 0, 0, 0, 3395, 3397, 1, 0, 0, 0, 3396, 3398, 3, 312, 156, 0, 3397, 3396, 1, 0, 0, 0, 3397, 3398, 1, 0, 0, 0, 3398, 3399, 1, 0, 0, 0, 3399, 3401, 5, 3, 0, 0, 3400, 3354, 1, 0, 0, 0, 3400, 3355, 1, 0, 0, 0, 3400, 3359, 1, 0, 0, 0, 3401, 311, 1, 0, 0, 0, 3402, 3403, 5, 206, 0, 0, 3403, 3419, 3, 314, 157, 0, 3404, 3405, 5, 228, 0, 0, 3405, 3419, 3, 314, 157, 0, 3406, 3407, 5, 206, 0, 0, 3407, 3408, 5, 24, 0, 0, 3408, 3409, 3, 314, 157, 0, 3409, 3410, 5, 14, 0, 0, 3410, 3411, 3, 314, 157, 0, 3411, 3419, 1, 0, 0, 0, 3412, 3413, 5, 228, 0, 0, 3413, 3414, 5, 24, 0, 0, 3414, 3415, 3, 314, 157, 0, 3415, 3416, 5, 14, 0, 0, 3416, 3417, 3, 314, 157, 0, 3417, 3419, 1, 0, 0, 0, 3418, 3402, 1, 0, 0, 0, 3418, 3404, 1, 0, 0, 0, 3418, 3406, 1, 0, 0, 0, 3418, 3412, 1, 0, 0, 0, 3419, 313, 1, 0, 0, 0, 3420, 3421, 5, 282, 0, 0, 3421, 3428, 7, 51, 0, 0, 3422, 3423, 5, 56, 0, 0, 3423, 3428, 5, 227, 0, 0, 3424, 3425, 3, 236, 118, 0, 3425, 3426, 7, 51, 0, 0, 3426, 3428, 1, 0, 0, 0, 3427, 3420, 1, 0, 0, 0, 3427, 3422, 1, 0, 0, 0, 3427, 3424, 1, 0, 0, 0, 3428, 315, 1, 0, 0, 0, 3429, 3434, 3, 320, 160, 0, 3430, 3431, 5, 4, 0, 0, 3431, 3433, 3, 320, 160, 0, 3432, 3430, 1, 0, 0, 0, 3433, 3436, 1, 0, 0, 0, 3434, 3432, 1, 0, 0, 0, 3434, 3435, 1, 0, 0, 0, 3435, 317, 1, 0, 0, 0, 3436, 3434, 1, 0, 0, 0, 3437, 3442, 3, 320, 160, 0, 3438, 3442, 5, 99, 0, 0, 3439, 3442, 5, 141, 0, 0, 3440, 3442, 5, 221, 0, 0, 3441, 3437, 1, 0, 0, 0, 3441, 3438, 1, 0, 0, 0, 3441, 3439, 1, 0, 0, 0, 3441, 3440, 1, 0, 0, 0, 3442, 319, 1, 0, 0, 0, 3443, 3448, 3, 326, 163, 0, 3444, 3445, 5, 5, 0, 0, 3445, 3447, 3, 326, 163, 0, 3446, 3444, 1, 0, 0, 0, 3447, 3450, 1, 0, 0, 0, 3448, 3446, 1, 0, 0, 0, 3448, 3449, 1, 0, 0, 0, 3449, 321, 1, 0, 0, 0, 3450, 3448, 1, 0, 0, 0, 3451, 3452, 3, 326, 163, 0, 3452, 3453, 3, 324, 162, 0, 3453, 323, 1, 0, 0, 0, 3454, 3455, 5, 317, 0, 0, 3455, 3457, 3, 326, 163, 0, 3456, 3454, 1, 0, 0, 0, 3457, 3458, 1, 0, 0, 0, 3458, 3456, 1, 0, 0, 0, 3458, 3459, 1, 0, 0, 0, 3459, 3462, 1, 0, 0, 0, 3460, 3462, 1, 0, 0, 0, 3461, 3456, 1, 0, 0, 0, 3461, 3460, 1, 0, 0, 0, 3462, 325, 1, 0, 0, 0, 3463, 3466, 3, 328, 164, 0, 3464, 3466, 3, 346, 173, 0, 3465, 3463, 1, 0, 0, 0, 3465, 3464, 1, 0, 0, 0, 3466, 327, 1, 0, 0, 0, 3467, 3472, 5, 341, 0, 0, 3468, 3472, 3, 330, 165, 0, 3469, 3472, 3, 344, 172, 0, 3470, 3472, 3, 348, 174, 0, 3471, 3467, 1, 0, 0, 0, 3471, 3468, 1, 0, 0, 0, 3471, 3469, 1, 0, 0, 0, 3471, 3470, 1, 0, 0, 0, 3472, 329, 1, 0, 0, 0, 3473, 3474, 7, 52, 0, 0, 3474, 331, 1, 0, 0, 0, 3475, 3476, 5, 342, 0, 0, 3476, 333, 1, 0, 0, 0, 3477, 3479, 5, 317, 0, 0, 3478, 3477, 1, 0, 0, 0, 3478, 3479, 1, 0, 0, 0, 3479, 3480, 1, 0, 0, 0, 3480, 3518, 5, 336, 0, 0, 3481, 3483, 5, 317, 0, 0, 3482, 3481, 1, 0, 0, 0, 3482, 3483, 1, 0, 0, 0, 3483, 3484, 1, 0, 0, 0, 3484, 3518, 5, 337, 0, 0, 3485, 3487, 5, 317, 0, 0, 3486, 3485, 1, 0, 0, 0, 3486, 3487, 1, 0, 0, 0, 3487, 3488, 1, 0, 0, 0, 3488, 3518, 7, 53, 0, 0, 3489, 3491, 5, 317, 0, 0, 3490, 3489, 1, 0, 0, 0, 3490, 3491, 1, 0, 0, 0, 3491, 3492, 1, 0, 0, 0, 3492, 3518, 5, 335, 0, 0, 3493, 3495, 5, 317, 0, 0, 3494, 3493, 1, 0, 0, 0, 3494, 3495, 1, 0, 0, 0, 3495, 3496, 1, 0, 0, 0, 3496, 3518, 5, 332, 0, 0, 3497, 3499, 5, 317, 0, 0, 3498, 3497, 1, 0, 0, 0, 3498, 3499, 1, 0, 0, 0, 3499, 3500, 1, 0, 0, 0, 3500, 3518, 5, 333, 0, 0, 3501, 3503, 5, 317, 0, 0, 3502, 3501, 1, 0, 0, 0, 3502, 3503, 1, 0, 0, 0, 3503, 3504, 1, 0, 0, 0, 3504, 3518, 5, 334, 0, 0, 3505, 3507, 5, 317, 0, 0, 3506, 3505, 1, 0, 0, 0, 3506, 3507, 1, 0, 0, 0, 3507, 3508, 1, 0, 0, 0, 3508, 3518, 5, 339, 0, 0, 3509, 3511, 5, 317, 0, 0, 3510, 3509, 1, 0, 0, 0, 3510, 3511, 1, 0, 0, 0, 3511, 3512, 1, 0, 0, 0, 3512, 3518, 5, 338, 0, 0, 3513, 3515, 5, 317, 0, 0, 3514, 3513, 1, 0, 0, 0, 3514, 3515, 1, 0, 0, 0, 3515, 3516, 1, 0, 0, 0, 3516, 3518, 5, 340, 0, 0, 3517, 3478, 1, 0, 0, 0, 3517, 3482, 1, 0, 0, 0, 3517, 3486, 1, 0, 0, 0, 3517, 3490, 1, 0, 0, 0, 3517, 3494, 1, 0, 0, 0, 3517, 3498, 1, 0, 0, 0, 3517, 3502, 1, 0, 0, 0, 3517, 3506, 1, 0, 0, 0, 3517, 3510, 1, 0, 0, 0, 3517, 3514, 1, 0, 0, 0, 3518, 335, 1, 0, 0, 0, 3519, 3520, 5, 280, 0, 0, 3520, 3531, 3, 278, 139, 0, 3521, 3531, 3, 34, 17, 0, 3522, 3531, 3, 276, 138, 0, 3523, 3524, 7, 54, 0, 0, 3524, 3525, 5, 172, 0, 0, 3525, 3531, 5, 173, 0, 0, 3526, 3527, 5, 239, 0, 0, 3527, 3531, 3, 286, 143, 0, 3528, 3529, 5, 82, 0, 0, 3529, 3531, 5, 70, 0, 0, 3530, 3519, 1, 0, 0, 0, 3530, 3521, 1, 0, 0, 0, 3530, 3522, 1, 0, 0, 0, 3530, 3523, 1, 0, 0, 0, 3530, 3526, 1, 0, 0, 0, 3530, 3528, 1, 0, 0, 0, 3531, 337, 1, 0, 0, 0, 3532, 3533, 7, 55, 0, 0, 3533, 339, 1, 0, 0, 0, 3534, 3537, 3, 338, 169, 0, 3535, 3537, 5, 173, 0, 0, 3536, 3534, 1, 0, 0, 0, 3536, 3535, 1, 0, 0, 0, 3537, 341, 1, 0, 0, 0, 3538, 3541, 5, 335, 0, 0, 3539, 3541, 3, 338, 169, 0, 3540, 3538, 1, 0, 0, 0, 3540, 3539, 1, 0, 0, 0, 3541, 343, 1, 0, 0, 0, 3542, 3543, 7, 56, 0, 0, 3543, 345, 1, 0, 0, 0, 3544, 3545, 7, 57, 0, 0, 3545, 347, 1, 0, 0, 0, 3546, 3547, 7, 58, 0, 0, 3547, 349, 1, 0, 0, 0, 460, 354, 379, 392, 399, 407, 409, 429, 433, 439, 442, 445, 452, 455, 459, 462, 469, 480, 482, 490, 493, 497, 500, 506, 517, 523, 528, 562, 575, 600, 609, 613, 619, 623, 628, 634, 646, 654, 660, 673, 678, 694, 701, 705, 711, 726, 730, 736, 742, 745, 748, 754, 758, 766, 768, 777, 780, 789, 794, 800, 807, 810, 816, 827, 830, 834, 839, 844, 851, 854, 857, 864, 869, 878, 886, 892, 895, 898, 904, 908, 913, 916, 920, 922, 930, 938, 941, 946, 952, 958, 961, 965, 968, 972, 1000, 1003, 1007, 1013, 1016, 1019, 1025, 1033, 1038, 1044, 1050, 1053, 1060, 1067, 1075, 1092, 1106, 1109, 1115, 1124, 1133, 1141, 1146, 1151, 1158, 1164, 1169, 1177, 1180, 1184, 1196, 1200, 1207, 1323, 1331, 1339, 1348, 1358, 1362, 1365, 1371, 1377, 1389, 1401, 1406, 1415, 1423, 1430, 1432, 1435, 1440, 1444, 1449, 1452, 1457, 1466, 1471, 1474, 1479, 1483, 1488, 1490, 1494, 1503, 1511, 1517, 1528, 1535, 1544, 1549, 1552, 1574, 1576, 1585, 1592, 1595, 1602, 1606, 1612, 1620, 1631, 1642, 1650, 1656, 1668, 1675, 1682, 1694, 1702, 1708, 1714, 1717, 1726, 1729, 1738, 1741, 1750, 1753, 1762, 1765, 1768, 1773, 1775, 1779, 1790, 1795, 1807, 1811, 1815, 1821, 1825, 1833, 1837, 1840, 1843, 1846, 1850, 1854, 1859, 1863, 1866, 1869, 1872, 1876, 1881, 1885, 1888, 1891, 1894, 1896, 1902, 1909, 1914, 1917, 1920, 1924, 1934, 1938, 1940, 1943, 1947, 1953, 1957, 1968, 1978, 1982, 1994, 2006, 2021, 2026, 2032, 2039, 2055, 2060, 2073, 2078, 2086, 2092, 2096, 2099, 2102, 2109, 2115, 2124, 2134, 2149, 2154, 2156, 2160, 2169, 2182, 2187, 2191, 2199, 2202, 2206, 2220, 2233, 2238, 2242, 2245, 2249, 2255, 2258, 2265, 2277, 2288, 2301, 2312, 2317, 2325, 2330, 2337, 2346, 2349, 2354, 2361, 2364, 2369, 2375, 2381, 2386, 2390, 2396, 2400, 2403, 2408, 2411, 2416, 2420, 2423, 2426, 2432, 2437, 2444, 2447, 2465, 2467, 2470, 2481, 2490, 2497, 2505, 2512, 2516, 2519, 2527, 2535, 2541, 2549, 2561, 2564, 2570, 2574, 2576, 2585, 2597, 2599, 2606, 2613, 2619, 2625, 2627, 2634, 2642, 2650, 2656, 2661, 2668, 2674, 2678, 2680, 2687, 2696, 2703, 2713, 2718, 2722, 2731, 2744, 2746, 2754, 2756, 2760, 2768, 2777, 2783, 2791, 2796, 2808, 2813, 2816, 2822, 2826, 2831, 2836, 2841, 2847, 2868, 2870, 2899, 2903, 2912, 2916, 2934, 2937, 2945, 2954, 2963, 2986, 2997, 3004, 3007, 3016, 3020, 3024, 3036, 3061, 3068, 3071, 3086, 3107, 3111, 3113, 3123, 3125, 3140, 3142, 3155, 3159, 3166, 3171, 3179, 3184, 3193, 3210, 3214, 3220, 3226, 3235, 3239, 3241, 3248, 3256, 3264, 3274, 3281, 3284, 3291, 3299, 3307, 3321, 3326, 3331, 3334, 3347, 3367, 3377, 3380, 3389, 3392, 3394, 3397, 3400, 3418, 3427, 3434, 3441, 3448, 3458, 3461, 3465, 3471, 3478, 3482, 3486, 3490, 3494, 3498, 3502, 3506, 3510, 3514, 3517, 3530, 3536, 3540] diff --git a/datajunction-server/datajunction_server/sql/parsing/backends/grammar/generated/SqlBaseParser.py b/datajunction-server/datajunction_server/sql/parsing/backends/grammar/generated/SqlBaseParser.py new file mode 100644 index 000000000..367bc3cd5 --- /dev/null +++ b/datajunction-server/datajunction_server/sql/parsing/backends/grammar/generated/SqlBaseParser.py @@ -0,0 +1,27024 @@ +# Generated from SqlBaseParser.g4 by ANTLR 4.12.0 +# encoding: utf-8 +from antlr4 import * +from io import StringIO +import sys +if sys.version_info[1] > 5: + from typing import TextIO +else: + from typing.io import TextIO + +def serializedATN(): + return [ + 4,1,346,3549,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6, + 7,6,2,7,7,7,2,8,7,8,2,9,7,9,2,10,7,10,2,11,7,11,2,12,7,12,2,13,7, + 13,2,14,7,14,2,15,7,15,2,16,7,16,2,17,7,17,2,18,7,18,2,19,7,19,2, + 20,7,20,2,21,7,21,2,22,7,22,2,23,7,23,2,24,7,24,2,25,7,25,2,26,7, + 26,2,27,7,27,2,28,7,28,2,29,7,29,2,30,7,30,2,31,7,31,2,32,7,32,2, + 33,7,33,2,34,7,34,2,35,7,35,2,36,7,36,2,37,7,37,2,38,7,38,2,39,7, + 39,2,40,7,40,2,41,7,41,2,42,7,42,2,43,7,43,2,44,7,44,2,45,7,45,2, + 46,7,46,2,47,7,47,2,48,7,48,2,49,7,49,2,50,7,50,2,51,7,51,2,52,7, + 52,2,53,7,53,2,54,7,54,2,55,7,55,2,56,7,56,2,57,7,57,2,58,7,58,2, + 59,7,59,2,60,7,60,2,61,7,61,2,62,7,62,2,63,7,63,2,64,7,64,2,65,7, + 65,2,66,7,66,2,67,7,67,2,68,7,68,2,69,7,69,2,70,7,70,2,71,7,71,2, + 72,7,72,2,73,7,73,2,74,7,74,2,75,7,75,2,76,7,76,2,77,7,77,2,78,7, + 78,2,79,7,79,2,80,7,80,2,81,7,81,2,82,7,82,2,83,7,83,2,84,7,84,2, + 85,7,85,2,86,7,86,2,87,7,87,2,88,7,88,2,89,7,89,2,90,7,90,2,91,7, + 91,2,92,7,92,2,93,7,93,2,94,7,94,2,95,7,95,2,96,7,96,2,97,7,97,2, + 98,7,98,2,99,7,99,2,100,7,100,2,101,7,101,2,102,7,102,2,103,7,103, + 2,104,7,104,2,105,7,105,2,106,7,106,2,107,7,107,2,108,7,108,2,109, + 7,109,2,110,7,110,2,111,7,111,2,112,7,112,2,113,7,113,2,114,7,114, + 2,115,7,115,2,116,7,116,2,117,7,117,2,118,7,118,2,119,7,119,2,120, + 7,120,2,121,7,121,2,122,7,122,2,123,7,123,2,124,7,124,2,125,7,125, + 2,126,7,126,2,127,7,127,2,128,7,128,2,129,7,129,2,130,7,130,2,131, + 7,131,2,132,7,132,2,133,7,133,2,134,7,134,2,135,7,135,2,136,7,136, + 2,137,7,137,2,138,7,138,2,139,7,139,2,140,7,140,2,141,7,141,2,142, + 7,142,2,143,7,143,2,144,7,144,2,145,7,145,2,146,7,146,2,147,7,147, + 2,148,7,148,2,149,7,149,2,150,7,150,2,151,7,151,2,152,7,152,2,153, + 7,153,2,154,7,154,2,155,7,155,2,156,7,156,2,157,7,157,2,158,7,158, + 2,159,7,159,2,160,7,160,2,161,7,161,2,162,7,162,2,163,7,163,2,164, + 7,164,2,165,7,165,2,166,7,166,2,167,7,167,2,168,7,168,2,169,7,169, + 2,170,7,170,2,171,7,171,2,172,7,172,2,173,7,173,2,174,7,174,1,0, + 1,0,5,0,353,8,0,10,0,12,0,356,9,0,1,0,1,0,1,1,1,1,1,1,1,2,1,2,1, + 2,1,3,1,3,1,3,1,4,1,4,1,4,1,5,1,5,1,5,1,6,1,6,1,6,1,7,1,7,3,7,380, + 8,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,3,7,393,8,7,1,7, + 1,7,1,7,1,7,1,7,3,7,400,8,7,1,7,1,7,1,7,1,7,1,7,1,7,5,7,408,8,7, + 10,7,12,7,411,9,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1, + 7,1,7,1,7,1,7,1,7,1,7,3,7,430,8,7,1,7,1,7,3,7,434,8,7,1,7,1,7,1, + 7,1,7,3,7,440,8,7,1,7,3,7,443,8,7,1,7,3,7,446,8,7,1,7,1,7,1,7,1, + 7,1,7,3,7,453,8,7,1,7,3,7,456,8,7,1,7,1,7,3,7,460,8,7,1,7,3,7,463, + 8,7,1,7,1,7,1,7,1,7,1,7,3,7,470,8,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7, + 1,7,1,7,5,7,481,8,7,10,7,12,7,484,9,7,1,7,1,7,1,7,1,7,1,7,3,7,491, + 8,7,1,7,3,7,494,8,7,1,7,1,7,3,7,498,8,7,1,7,3,7,501,8,7,1,7,1,7, + 1,7,1,7,3,7,507,8,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,3,7,518, + 8,7,1,7,1,7,1,7,1,7,3,7,524,8,7,1,7,1,7,1,7,3,7,529,8,7,1,7,1,7, + 1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7, + 1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,3,7,563, + 8,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,3,7,576,8,7,1,7, + 1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7, + 1,7,1,7,1,7,1,7,1,7,1,7,3,7,601,8,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7, + 3,7,610,8,7,1,7,1,7,3,7,614,8,7,1,7,1,7,1,7,1,7,3,7,620,8,7,1,7, + 1,7,3,7,624,8,7,1,7,1,7,1,7,3,7,629,8,7,1,7,1,7,1,7,1,7,3,7,635, + 8,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,3,7,647,8,7,1,7,1,7, + 1,7,1,7,1,7,1,7,3,7,655,8,7,1,7,1,7,1,7,1,7,3,7,661,8,7,1,7,1,7, + 1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,3,7,674,8,7,1,7,4,7,677,8,7, + 11,7,12,7,678,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1, + 7,1,7,3,7,695,8,7,1,7,1,7,1,7,5,7,700,8,7,10,7,12,7,703,9,7,1,7, + 3,7,706,8,7,1,7,1,7,1,7,1,7,3,7,712,8,7,1,7,1,7,1,7,1,7,1,7,1,7, + 1,7,1,7,1,7,1,7,1,7,1,7,1,7,3,7,727,8,7,1,7,1,7,3,7,731,8,7,1,7, + 1,7,1,7,1,7,3,7,737,8,7,1,7,1,7,1,7,1,7,3,7,743,8,7,1,7,3,7,746, + 8,7,1,7,3,7,749,8,7,1,7,1,7,1,7,1,7,3,7,755,8,7,1,7,1,7,3,7,759, + 8,7,1,7,1,7,1,7,1,7,1,7,1,7,5,7,767,8,7,10,7,12,7,770,9,7,1,7,1, + 7,1,7,1,7,1,7,1,7,3,7,778,8,7,1,7,3,7,781,8,7,1,7,1,7,1,7,1,7,1, + 7,1,7,1,7,3,7,790,8,7,1,7,1,7,1,7,3,7,795,8,7,1,7,1,7,1,7,1,7,3, + 7,801,8,7,1,7,1,7,1,7,1,7,1,7,3,7,808,8,7,1,7,3,7,811,8,7,1,7,1, + 7,1,7,1,7,3,7,817,8,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,5,7,826,8,7,10, + 7,12,7,829,9,7,3,7,831,8,7,1,7,1,7,3,7,835,8,7,1,7,1,7,1,7,3,7,840, + 8,7,1,7,1,7,1,7,3,7,845,8,7,1,7,1,7,1,7,1,7,1,7,3,7,852,8,7,1,7, + 3,7,855,8,7,1,7,3,7,858,8,7,1,7,1,7,1,7,1,7,1,7,3,7,865,8,7,1,7, + 1,7,1,7,3,7,870,8,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,3,7,879,8,7,1,7, + 1,7,1,7,1,7,1,7,1,7,3,7,887,8,7,1,7,1,7,1,7,1,7,3,7,893,8,7,1,7, + 3,7,896,8,7,1,7,3,7,899,8,7,1,7,1,7,1,7,1,7,3,7,905,8,7,1,7,1,7, + 3,7,909,8,7,1,7,1,7,1,7,3,7,914,8,7,1,7,3,7,917,8,7,1,7,1,7,3,7, + 921,8,7,3,7,923,8,7,1,7,1,7,1,7,1,7,1,7,1,7,3,7,931,8,7,1,7,1,7, + 1,7,1,7,1,7,1,7,3,7,939,8,7,1,7,3,7,942,8,7,1,7,1,7,1,7,3,7,947, + 8,7,1,7,1,7,1,7,1,7,3,7,953,8,7,1,7,1,7,1,7,1,7,3,7,959,8,7,1,7, + 3,7,962,8,7,1,7,1,7,3,7,966,8,7,1,7,3,7,969,8,7,1,7,1,7,3,7,973, + 8,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7, + 1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,5,7,999,8,7,10,7,12,7,1002,9, + 7,3,7,1004,8,7,1,7,1,7,3,7,1008,8,7,1,7,1,7,1,7,1,7,3,7,1014,8,7, + 1,7,3,7,1017,8,7,1,7,3,7,1020,8,7,1,7,1,7,1,7,1,7,3,7,1026,8,7,1, + 7,1,7,1,7,1,7,1,7,1,7,3,7,1034,8,7,1,7,1,7,1,7,3,7,1039,8,7,1,7, + 1,7,1,7,1,7,3,7,1045,8,7,1,7,1,7,1,7,1,7,3,7,1051,8,7,1,7,3,7,1054, + 8,7,1,7,1,7,1,7,1,7,1,7,3,7,1061,8,7,1,7,1,7,1,7,5,7,1066,8,7,10, + 7,12,7,1069,9,7,1,7,1,7,1,7,5,7,1074,8,7,10,7,12,7,1077,9,7,1,7, + 1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,5,7,1091,8,7,10,7,12, + 7,1094,9,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,5,7,1105,8,7,10,7, + 12,7,1108,9,7,3,7,1110,8,7,1,7,1,7,5,7,1114,8,7,10,7,12,7,1117,9, + 7,1,7,1,7,1,7,1,7,5,7,1123,8,7,10,7,12,7,1126,9,7,1,7,1,7,1,7,1, + 7,5,7,1132,8,7,10,7,12,7,1135,9,7,1,7,1,7,1,7,1,7,1,7,3,7,1142,8, + 7,1,7,1,7,1,7,3,7,1147,8,7,1,7,1,7,1,7,3,7,1152,8,7,1,7,1,7,1,7, + 1,7,1,7,3,7,1159,8,7,1,7,1,7,1,7,1,7,3,7,1165,8,7,1,7,1,7,1,7,3, + 7,1170,8,7,1,7,1,7,1,7,1,7,5,7,1176,8,7,10,7,12,7,1179,9,7,3,7,1181, + 8,7,1,8,1,8,3,8,1185,8,8,1,9,1,9,1,10,1,10,1,11,1,11,1,11,1,11,1, + 11,1,11,3,11,1197,8,11,1,11,1,11,3,11,1201,8,11,1,11,1,11,1,11,1, + 11,1,11,3,11,1208,8,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1, + 11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1, + 11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1, + 11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1, + 11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1, + 11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1, + 11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1, + 11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1, + 11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1,11,1, + 11,1,11,3,11,1324,8,11,1,11,1,11,1,11,1,11,1,11,1,11,3,11,1332,8, + 11,1,11,1,11,1,11,1,11,1,11,1,11,3,11,1340,8,11,1,11,1,11,1,11,1, + 11,1,11,1,11,1,11,3,11,1349,8,11,1,11,1,11,1,11,1,11,1,11,1,11,1, + 11,1,11,3,11,1359,8,11,1,12,1,12,3,12,1363,8,12,1,12,3,12,1366,8, + 12,1,12,1,12,1,12,1,12,3,12,1372,8,12,1,12,1,12,1,13,1,13,3,13,1378, + 8,13,1,13,1,13,1,13,1,13,1,14,1,14,1,14,1,14,1,14,1,14,3,14,1390, + 8,14,1,14,1,14,1,14,1,14,1,15,1,15,1,15,1,15,1,15,1,15,3,15,1402, + 8,15,1,15,1,15,1,15,3,15,1407,8,15,1,16,1,16,1,16,1,17,1,17,1,17, + 1,18,3,18,1416,8,18,1,18,1,18,1,18,1,19,1,19,1,19,3,19,1424,8,19, + 1,19,1,19,1,19,1,19,1,19,3,19,1431,8,19,3,19,1433,8,19,1,19,3,19, + 1436,8,19,1,19,1,19,1,19,3,19,1441,8,19,1,19,1,19,3,19,1445,8,19, + 1,19,1,19,1,19,3,19,1450,8,19,1,19,3,19,1453,8,19,1,19,1,19,1,19, + 3,19,1458,8,19,1,19,1,19,1,19,1,19,1,19,1,19,1,19,3,19,1467,8,19, + 1,19,1,19,1,19,3,19,1472,8,19,1,19,3,19,1475,8,19,1,19,1,19,1,19, + 3,19,1480,8,19,1,19,1,19,3,19,1484,8,19,1,19,1,19,1,19,3,19,1489, + 8,19,3,19,1491,8,19,1,20,1,20,3,20,1495,8,20,1,21,1,21,1,21,1,21, + 1,21,5,21,1502,8,21,10,21,12,21,1505,9,21,1,21,1,21,1,22,1,22,1, + 22,3,22,1512,8,22,1,22,1,22,1,22,1,22,3,22,1518,8,22,1,23,1,23,1, + 24,1,24,1,25,1,25,1,25,1,25,1,25,3,25,1529,8,25,1,26,1,26,1,26,5, + 26,1534,8,26,10,26,12,26,1537,9,26,1,27,1,27,1,27,1,27,5,27,1543, + 8,27,10,27,12,27,1546,9,27,1,28,1,28,3,28,1550,8,28,1,28,3,28,1553, + 8,28,1,28,1,28,1,28,1,28,1,29,1,29,1,29,1,30,1,30,1,30,1,30,1,30, + 1,30,1,30,1,30,1,30,1,30,1,30,1,30,1,30,5,30,1575,8,30,10,30,12, + 30,1578,9,30,1,31,1,31,1,31,1,31,5,31,1584,8,31,10,31,12,31,1587, + 9,31,1,31,1,31,1,32,1,32,3,32,1593,8,32,1,32,3,32,1596,8,32,1,33, + 1,33,1,33,5,33,1601,8,33,10,33,12,33,1604,9,33,1,33,3,33,1607,8, + 33,1,34,1,34,1,34,1,34,3,34,1613,8,34,1,35,1,35,1,35,1,35,5,35,1619, + 8,35,10,35,12,35,1622,9,35,1,35,1,35,1,36,1,36,1,36,1,36,5,36,1630, + 8,36,10,36,12,36,1633,9,36,1,36,1,36,1,37,1,37,1,37,1,37,1,37,1, + 37,3,37,1643,8,37,1,38,1,38,1,38,1,38,1,38,1,38,3,38,1651,8,38,1, + 39,1,39,1,39,1,39,3,39,1657,8,39,1,40,1,40,1,40,1,41,1,41,1,41,1, + 41,1,41,4,41,1667,8,41,11,41,12,41,1668,1,41,1,41,1,41,1,41,1,41, + 3,41,1676,8,41,1,41,1,41,1,41,1,41,1,41,3,41,1683,8,41,1,41,1,41, + 1,41,1,41,1,41,1,41,1,41,1,41,1,41,1,41,3,41,1695,8,41,1,41,1,41, + 1,41,1,41,5,41,1701,8,41,10,41,12,41,1704,9,41,1,41,5,41,1707,8, + 41,10,41,12,41,1710,9,41,1,41,5,41,1713,8,41,10,41,12,41,1716,9, + 41,3,41,1718,8,41,1,42,1,42,1,42,1,42,1,42,5,42,1725,8,42,10,42, + 12,42,1728,9,42,3,42,1730,8,42,1,42,1,42,1,42,1,42,1,42,5,42,1737, + 8,42,10,42,12,42,1740,9,42,3,42,1742,8,42,1,42,1,42,1,42,1,42,1, + 42,5,42,1749,8,42,10,42,12,42,1752,9,42,3,42,1754,8,42,1,42,1,42, + 1,42,1,42,1,42,5,42,1761,8,42,10,42,12,42,1764,9,42,3,42,1766,8, + 42,1,42,3,42,1769,8,42,1,42,1,42,1,42,3,42,1774,8,42,3,42,1776,8, + 42,1,42,1,42,3,42,1780,8,42,1,43,1,43,1,43,1,44,1,44,1,44,1,44,1, + 44,1,44,3,44,1791,8,44,1,44,5,44,1794,8,44,10,44,12,44,1797,9,44, + 1,45,1,45,1,45,1,45,1,45,1,45,1,45,1,45,1,45,3,45,1808,8,45,1,46, + 1,46,3,46,1812,8,46,1,46,1,46,3,46,1816,8,46,1,47,1,47,4,47,1820, + 8,47,11,47,12,47,1821,1,48,1,48,3,48,1826,8,48,1,48,1,48,1,48,1, + 48,5,48,1832,8,48,10,48,12,48,1835,9,48,1,48,3,48,1838,8,48,1,48, + 3,48,1841,8,48,1,48,3,48,1844,8,48,1,48,3,48,1847,8,48,1,48,1,48, + 3,48,1851,8,48,1,49,1,49,3,49,1855,8,49,1,49,5,49,1858,8,49,10,49, + 12,49,1861,9,49,1,49,3,49,1864,8,49,1,49,3,49,1867,8,49,1,49,3,49, + 1870,8,49,1,49,3,49,1873,8,49,1,49,1,49,3,49,1877,8,49,1,49,5,49, + 1880,8,49,10,49,12,49,1883,9,49,1,49,3,49,1886,8,49,1,49,3,49,1889, + 8,49,1,49,3,49,1892,8,49,1,49,3,49,1895,8,49,3,49,1897,8,49,1,50, + 1,50,1,50,1,50,3,50,1903,8,50,1,50,1,50,1,50,1,50,1,50,3,50,1910, + 8,50,1,50,1,50,1,50,3,50,1915,8,50,1,50,3,50,1918,8,50,1,50,3,50, + 1921,8,50,1,50,1,50,3,50,1925,8,50,1,50,1,50,1,50,1,50,1,50,1,50, + 1,50,1,50,3,50,1935,8,50,1,50,1,50,3,50,1939,8,50,3,50,1941,8,50, + 1,50,3,50,1944,8,50,1,50,1,50,3,50,1948,8,50,1,51,1,51,5,51,1952, + 8,51,10,51,12,51,1955,9,51,1,51,3,51,1958,8,51,1,51,1,51,1,52,1, + 52,1,52,1,53,1,53,1,53,1,53,3,53,1969,8,53,1,53,1,53,1,53,1,54,1, + 54,1,54,1,54,1,54,3,54,1979,8,54,1,54,1,54,3,54,1983,8,54,1,54,1, + 54,1,54,1,55,1,55,1,55,1,55,1,55,1,55,1,55,3,55,1995,8,55,1,55,1, + 55,1,55,1,56,1,56,1,56,1,56,1,56,1,56,1,56,3,56,2007,8,56,1,57,1, + 57,1,57,1,57,1,57,1,57,1,57,1,57,1,57,1,57,1,57,5,57,2020,8,57,10, + 57,12,57,2023,9,57,1,57,1,57,3,57,2027,8,57,1,58,1,58,1,58,1,58, + 3,58,2033,8,58,1,59,1,59,1,59,5,59,2038,8,59,10,59,12,59,2041,9, + 59,1,60,1,60,1,60,1,60,1,61,1,61,1,61,1,62,1,62,1,62,1,63,1,63,1, + 63,3,63,2056,8,63,1,63,5,63,2059,8,63,10,63,12,63,2062,9,63,1,63, + 1,63,1,64,1,64,1,64,1,64,1,64,1,64,5,64,2072,8,64,10,64,12,64,2075, + 9,64,1,64,1,64,3,64,2079,8,64,1,65,1,65,1,65,1,65,5,65,2085,8,65, + 10,65,12,65,2088,9,65,1,65,5,65,2091,8,65,10,65,12,65,2094,9,65, + 1,65,3,65,2097,8,65,1,65,3,65,2100,8,65,1,66,3,66,2103,8,66,1,66, + 1,66,1,66,1,66,1,66,3,66,2110,8,66,1,66,1,66,1,66,1,66,3,66,2116, + 8,66,1,67,1,67,1,67,1,67,1,67,5,67,2123,8,67,10,67,12,67,2126,9, + 67,1,67,1,67,1,67,1,67,1,67,5,67,2133,8,67,10,67,12,67,2136,9,67, + 1,67,1,67,1,67,1,67,1,67,1,67,1,67,1,67,1,67,1,67,5,67,2148,8,67, + 10,67,12,67,2151,9,67,1,67,1,67,3,67,2155,8,67,3,67,2157,8,67,1, + 68,1,68,3,68,2161,8,68,1,69,1,69,1,69,1,69,1,69,5,69,2168,8,69,10, + 69,12,69,2171,9,69,1,69,1,69,1,69,1,69,1,69,1,69,1,69,1,69,5,69, + 2181,8,69,10,69,12,69,2184,9,69,1,69,1,69,3,69,2188,8,69,1,70,1, + 70,3,70,2192,8,70,1,71,1,71,1,71,1,71,5,71,2198,8,71,10,71,12,71, + 2201,9,71,3,71,2203,8,71,1,71,1,71,3,71,2207,8,71,1,72,1,72,1,72, + 1,72,1,72,1,72,1,72,1,72,1,72,1,72,5,72,2219,8,72,10,72,12,72,2222, + 9,72,1,72,1,72,1,72,1,73,1,73,1,73,1,73,1,73,5,73,2232,8,73,10,73, + 12,73,2235,9,73,1,73,1,73,3,73,2239,8,73,1,74,1,74,3,74,2243,8,74, + 1,74,3,74,2246,8,74,1,75,1,75,3,75,2250,8,75,1,75,1,75,1,75,1,75, + 3,75,2256,8,75,1,75,3,75,2259,8,75,1,76,1,76,1,76,1,77,1,77,3,77, + 2266,8,77,1,78,1,78,1,78,1,78,1,78,1,78,1,78,1,78,5,78,2276,8,78, + 10,78,12,78,2279,9,78,1,78,1,78,1,79,1,79,1,79,1,79,5,79,2287,8, + 79,10,79,12,79,2290,9,79,1,79,1,79,1,79,1,79,1,79,1,79,1,79,1,79, + 5,79,2300,8,79,10,79,12,79,2303,9,79,1,79,1,79,1,80,1,80,1,80,1, + 80,5,80,2311,8,80,10,80,12,80,2314,9,80,1,80,1,80,3,80,2318,8,80, + 1,81,1,81,1,82,1,82,1,83,1,83,3,83,2326,8,83,1,84,1,84,1,85,3,85, + 2331,8,85,1,85,1,85,1,86,1,86,1,86,3,86,2338,8,86,1,86,1,86,1,86, + 1,86,1,86,5,86,2345,8,86,10,86,12,86,2348,9,86,3,86,2350,8,86,1, + 86,1,86,1,86,3,86,2355,8,86,1,86,1,86,1,86,5,86,2360,8,86,10,86, + 12,86,2363,9,86,3,86,2365,8,86,1,87,1,87,1,88,3,88,2370,8,88,1,88, + 1,88,5,88,2374,8,88,10,88,12,88,2377,9,88,1,89,1,89,1,89,3,89,2382, + 8,89,1,90,1,90,1,90,3,90,2387,8,90,1,90,1,90,3,90,2391,8,90,1,90, + 1,90,1,90,1,90,3,90,2397,8,90,1,90,1,90,3,90,2401,8,90,1,91,3,91, + 2404,8,91,1,91,1,91,1,91,3,91,2409,8,91,1,91,3,91,2412,8,91,1,91, + 1,91,1,91,3,91,2417,8,91,1,91,1,91,3,91,2421,8,91,1,91,3,91,2424, + 8,91,1,91,3,91,2427,8,91,1,92,1,92,1,92,1,92,3,92,2433,8,92,1,93, + 1,93,1,93,3,93,2438,8,93,1,93,1,93,1,93,1,93,1,93,3,93,2445,8,93, + 1,94,3,94,2448,8,94,1,94,1,94,1,94,1,94,1,94,1,94,1,94,1,94,1,94, + 1,94,1,94,1,94,1,94,1,94,1,94,1,94,3,94,2466,8,94,3,94,2468,8,94, + 1,94,3,94,2471,8,94,1,95,1,95,1,95,1,95,1,96,1,96,1,96,5,96,2480, + 8,96,10,96,12,96,2483,9,96,1,97,1,97,1,97,1,97,5,97,2489,8,97,10, + 97,12,97,2492,9,97,1,97,1,97,1,98,1,98,3,98,2498,8,98,1,99,1,99, + 1,99,1,99,5,99,2504,8,99,10,99,12,99,2507,9,99,1,99,1,99,1,100,1, + 100,3,100,2513,8,100,1,101,1,101,3,101,2517,8,101,1,101,3,101,2520, + 8,101,1,101,1,101,1,101,1,101,1,101,1,101,3,101,2528,8,101,1,101, + 1,101,1,101,1,101,1,101,1,101,3,101,2536,8,101,1,101,1,101,1,101, + 1,101,3,101,2542,8,101,1,102,1,102,1,102,1,102,5,102,2548,8,102, + 10,102,12,102,2551,9,102,1,102,1,102,1,103,1,103,1,103,1,103,1,103, + 5,103,2560,8,103,10,103,12,103,2563,9,103,3,103,2565,8,103,1,103, + 1,103,1,103,1,104,3,104,2571,8,104,1,104,1,104,3,104,2575,8,104, + 3,104,2577,8,104,1,105,1,105,1,105,1,105,1,105,1,105,1,105,3,105, + 2586,8,105,1,105,1,105,1,105,1,105,1,105,1,105,1,105,1,105,1,105, + 1,105,3,105,2598,8,105,3,105,2600,8,105,1,105,1,105,1,105,1,105, + 1,105,3,105,2607,8,105,1,105,1,105,1,105,1,105,1,105,3,105,2614, + 8,105,1,105,1,105,1,105,1,105,3,105,2620,8,105,1,105,1,105,1,105, + 1,105,3,105,2626,8,105,3,105,2628,8,105,1,106,1,106,1,106,5,106, + 2633,8,106,10,106,12,106,2636,9,106,1,107,1,107,1,107,5,107,2641, + 8,107,10,107,12,107,2644,9,107,1,108,1,108,1,108,5,108,2649,8,108, + 10,108,12,108,2652,9,108,1,109,1,109,1,109,3,109,2657,8,109,1,110, + 1,110,1,110,3,110,2662,8,110,1,110,1,110,1,111,1,111,1,111,3,111, + 2669,8,111,1,111,1,111,1,112,1,112,3,112,2675,8,112,1,112,1,112, + 3,112,2679,8,112,3,112,2681,8,112,1,113,1,113,1,113,5,113,2686,8, + 113,10,113,12,113,2689,9,113,1,114,1,114,1,114,1,114,5,114,2695, + 8,114,10,114,12,114,2698,9,114,1,114,1,114,1,115,1,115,3,115,2704, + 8,115,1,116,1,116,1,116,1,116,1,116,1,116,5,116,2712,8,116,10,116, + 12,116,2715,9,116,1,116,1,116,3,116,2719,8,116,1,117,1,117,3,117, + 2723,8,117,1,118,1,118,1,119,1,119,1,119,5,119,2730,8,119,10,119, + 12,119,2733,9,119,1,120,1,120,1,120,1,120,1,120,1,120,1,120,1,120, + 1,120,1,120,3,120,2745,8,120,3,120,2747,8,120,1,120,1,120,1,120, + 1,120,1,120,1,120,5,120,2755,8,120,10,120,12,120,2758,9,120,1,121, + 3,121,2761,8,121,1,121,1,121,1,121,1,121,1,121,1,121,3,121,2769, + 8,121,1,121,1,121,1,121,1,121,1,121,5,121,2776,8,121,10,121,12,121, + 2779,9,121,1,121,1,121,1,121,3,121,2784,8,121,1,121,1,121,1,121, + 1,121,1,121,1,121,3,121,2792,8,121,1,121,1,121,1,121,3,121,2797, + 8,121,1,121,1,121,1,121,1,121,1,121,1,121,1,121,1,121,5,121,2807, + 8,121,10,121,12,121,2810,9,121,1,121,1,121,3,121,2814,8,121,1,121, + 3,121,2817,8,121,1,121,1,121,1,121,1,121,3,121,2823,8,121,1,121, + 1,121,3,121,2827,8,121,1,121,1,121,1,121,3,121,2832,8,121,1,121, + 1,121,1,121,3,121,2837,8,121,1,121,1,121,1,121,3,121,2842,8,121, + 1,122,1,122,1,122,1,122,3,122,2848,8,122,1,122,1,122,1,122,1,122, + 1,122,1,122,1,122,1,122,1,122,1,122,1,122,1,122,1,122,1,122,1,122, + 1,122,1,122,1,122,1,122,5,122,2869,8,122,10,122,12,122,2872,9,122, + 1,123,1,123,1,124,1,124,1,124,1,124,1,124,1,124,1,124,1,124,1,124, + 1,124,1,124,1,124,1,124,1,124,1,124,1,124,1,124,1,124,1,124,1,124, + 1,124,1,124,4,124,2898,8,124,11,124,12,124,2899,1,124,1,124,3,124, + 2904,8,124,1,124,1,124,1,124,1,124,1,124,4,124,2911,8,124,11,124, + 12,124,2912,1,124,1,124,3,124,2917,8,124,1,124,1,124,1,124,1,124, + 1,124,1,124,1,124,1,124,1,124,1,124,1,124,1,124,1,124,1,124,5,124, + 2933,8,124,10,124,12,124,2936,9,124,3,124,2938,8,124,1,124,1,124, + 1,124,1,124,1,124,1,124,3,124,2946,8,124,1,124,1,124,1,124,1,124, + 1,124,1,124,1,124,3,124,2955,8,124,1,124,1,124,1,124,1,124,1,124, + 1,124,1,124,3,124,2964,8,124,1,124,1,124,1,124,1,124,1,124,1,124, + 1,124,1,124,1,124,1,124,1,124,1,124,1,124,1,124,1,124,1,124,1,124, + 1,124,1,124,4,124,2985,8,124,11,124,12,124,2986,1,124,1,124,1,124, + 1,124,1,124,1,124,1,124,1,124,1,124,3,124,2998,8,124,1,124,1,124, + 1,124,5,124,3003,8,124,10,124,12,124,3006,9,124,3,124,3008,8,124, + 1,124,1,124,1,124,1,124,1,124,1,124,1,124,3,124,3017,8,124,1,124, + 1,124,3,124,3021,8,124,1,124,1,124,3,124,3025,8,124,1,124,1,124, + 1,124,1,124,1,124,1,124,1,124,1,124,4,124,3035,8,124,11,124,12,124, + 3036,1,124,1,124,1,124,1,124,1,124,1,124,1,124,1,124,1,124,1,124, + 1,124,1,124,1,124,1,124,1,124,1,124,1,124,1,124,1,124,1,124,1,124, + 1,124,1,124,3,124,3062,8,124,1,124,1,124,1,124,1,124,1,124,3,124, + 3069,8,124,1,124,3,124,3072,8,124,1,124,1,124,1,124,1,124,1,124, + 1,124,1,124,1,124,1,124,1,124,1,124,1,124,1,124,3,124,3087,8,124, + 1,124,1,124,1,124,1,124,1,124,1,124,1,124,1,124,1,124,1,124,1,124, + 1,124,1,124,1,124,1,124,1,124,1,124,1,124,1,124,3,124,3108,8,124, + 1,124,1,124,3,124,3112,8,124,3,124,3114,8,124,1,124,1,124,1,124, + 1,124,1,124,1,124,1,124,1,124,5,124,3124,8,124,10,124,12,124,3127, + 9,124,1,125,1,125,1,125,1,125,1,125,1,125,1,125,1,125,1,125,1,125, + 4,125,3139,8,125,11,125,12,125,3140,3,125,3143,8,125,1,126,1,126, + 1,127,1,127,1,128,1,128,1,129,1,129,1,130,1,130,1,130,3,130,3156, + 8,130,1,131,1,131,3,131,3160,8,131,1,132,1,132,1,132,4,132,3165, + 8,132,11,132,12,132,3166,1,133,1,133,1,133,3,133,3172,8,133,1,134, + 1,134,1,134,1,134,1,134,1,135,3,135,3180,8,135,1,135,1,135,1,135, + 3,135,3185,8,135,1,136,1,136,1,137,1,137,1,138,1,138,1,138,3,138, + 3194,8,138,1,139,1,139,1,139,1,139,1,139,1,139,1,139,1,139,1,139, + 1,139,1,139,1,139,1,139,1,139,1,139,3,139,3211,8,139,1,139,1,139, + 3,139,3215,8,139,1,139,1,139,1,139,1,139,3,139,3221,8,139,1,139, + 1,139,1,139,1,139,3,139,3227,8,139,1,139,1,139,1,139,1,139,1,139, + 5,139,3234,8,139,10,139,12,139,3237,9,139,1,139,3,139,3240,8,139, + 3,139,3242,8,139,1,140,1,140,1,140,5,140,3247,8,140,10,140,12,140, + 3250,9,140,1,141,1,141,1,141,5,141,3255,8,141,10,141,12,141,3258, + 9,141,1,142,1,142,1,142,1,142,1,142,3,142,3265,8,142,1,143,1,143, + 1,143,1,144,1,144,1,144,5,144,3273,8,144,10,144,12,144,3276,9,144, + 1,145,1,145,1,145,1,145,3,145,3282,8,145,1,145,3,145,3285,8,145, + 1,146,1,146,1,146,5,146,3290,8,146,10,146,12,146,3293,9,146,1,147, + 1,147,1,147,5,147,3298,8,147,10,147,12,147,3301,9,147,1,148,1,148, + 1,148,1,148,1,148,3,148,3308,8,148,1,149,1,149,1,149,1,149,1,149, + 1,149,1,149,1,150,1,150,1,150,5,150,3320,8,150,10,150,12,150,3323, + 9,150,1,151,1,151,3,151,3327,8,151,1,151,1,151,1,151,3,151,3332, + 8,151,1,151,3,151,3335,8,151,1,152,1,152,1,152,1,152,1,152,1,153, + 1,153,1,153,1,153,5,153,3346,8,153,10,153,12,153,3349,9,153,1,154, + 1,154,1,154,1,154,1,155,1,155,1,155,1,155,1,155,1,155,1,155,1,155, + 1,155,1,155,1,155,5,155,3366,8,155,10,155,12,155,3369,9,155,1,155, + 1,155,1,155,1,155,1,155,5,155,3376,8,155,10,155,12,155,3379,9,155, + 3,155,3381,8,155,1,155,1,155,1,155,1,155,1,155,5,155,3388,8,155, + 10,155,12,155,3391,9,155,3,155,3393,8,155,3,155,3395,8,155,1,155, + 3,155,3398,8,155,1,155,3,155,3401,8,155,1,156,1,156,1,156,1,156, + 1,156,1,156,1,156,1,156,1,156,1,156,1,156,1,156,1,156,1,156,1,156, + 1,156,3,156,3419,8,156,1,157,1,157,1,157,1,157,1,157,1,157,1,157, + 3,157,3428,8,157,1,158,1,158,1,158,5,158,3433,8,158,10,158,12,158, + 3436,9,158,1,159,1,159,1,159,1,159,3,159,3442,8,159,1,160,1,160, + 1,160,5,160,3447,8,160,10,160,12,160,3450,9,160,1,161,1,161,1,161, + 1,162,1,162,4,162,3457,8,162,11,162,12,162,3458,1,162,3,162,3462, + 8,162,1,163,1,163,3,163,3466,8,163,1,164,1,164,1,164,1,164,3,164, + 3472,8,164,1,165,1,165,1,166,1,166,1,167,3,167,3479,8,167,1,167, + 1,167,3,167,3483,8,167,1,167,1,167,3,167,3487,8,167,1,167,1,167, + 3,167,3491,8,167,1,167,1,167,3,167,3495,8,167,1,167,1,167,3,167, + 3499,8,167,1,167,1,167,3,167,3503,8,167,1,167,1,167,3,167,3507,8, + 167,1,167,1,167,3,167,3511,8,167,1,167,1,167,3,167,3515,8,167,1, + 167,3,167,3518,8,167,1,168,1,168,1,168,1,168,1,168,1,168,1,168,1, + 168,1,168,1,168,1,168,3,168,3531,8,168,1,169,1,169,1,170,1,170,3, + 170,3537,8,170,1,171,1,171,3,171,3541,8,171,1,172,1,172,1,173,1, + 173,1,174,1,174,1,174,9,1000,1067,1075,1092,1106,1115,1124,1133, + 1177,4,88,240,244,248,175,0,2,4,6,8,10,12,14,16,18,20,22,24,26,28, + 30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64,66,68,70,72, + 74,76,78,80,82,84,86,88,90,92,94,96,98,100,102,104,106,108,110,112, + 114,116,118,120,122,124,126,128,130,132,134,136,138,140,142,144, + 146,148,150,152,154,156,158,160,162,164,166,168,170,172,174,176, + 178,180,182,184,186,188,190,192,194,196,198,200,202,204,206,208, + 210,212,214,216,218,220,222,224,226,228,230,232,234,236,238,240, + 242,244,246,248,250,252,254,256,258,260,262,264,266,268,270,272, + 274,276,278,280,282,284,286,288,290,292,294,296,298,300,302,304, + 306,308,310,312,314,316,318,320,322,324,326,328,330,332,334,336, + 338,340,342,344,346,348,0,59,2,0,69,69,202,202,2,0,30,30,219,219, + 2,0,107,107,122,122,1,0,43,44,2,0,258,258,296,296,2,0,11,11,35,35, + 5,0,40,40,52,52,93,93,106,106,152,152,1,0,74,75,2,0,93,93,106,106, + 3,0,8,8,82,82,255,255,2,0,8,8,146,146,3,0,65,65,166,166,231,231, + 3,0,66,66,167,167,232,232,4,0,87,87,130,130,240,240,284,284,2,0, + 21,21,74,74,2,0,101,101,137,137,2,0,257,257,295,295,2,0,256,256, + 267,267,2,0,55,55,226,226,2,0,89,89,123,123,2,0,10,10,79,79,2,0, + 335,335,337,337,1,0,142,143,3,0,10,10,16,16,244,244,3,0,96,96,277, + 277,286,286,2,0,316,317,321,321,2,0,81,81,318,320,2,0,316,317,324, + 324,11,0,61,61,63,63,117,117,157,157,159,159,161,161,163,163,204, + 204,229,229,298,298,305,305,3,0,57,57,59,60,292,292,2,0,67,67,268, + 268,2,0,68,68,269,269,2,0,32,32,279,279,2,0,120,120,218,218,1,0, + 253,254,2,0,4,4,107,107,2,0,4,4,103,103,3,0,25,25,140,140,272,272, + 1,0,193,194,1,0,308,315,2,0,81,81,316,325,4,0,14,14,122,122,172, + 172,181,181,2,0,96,96,277,277,1,0,316,317,7,0,61,62,117,118,157, + 164,168,169,229,230,298,299,305,306,6,0,61,61,117,117,161,161,163, + 163,229,229,305,305,2,0,163,163,305,305,4,0,61,61,117,117,161,161, + 229,229,3,0,117,117,161,161,229,229,2,0,80,80,190,190,2,0,182,182, + 245,245,2,0,102,102,199,199,2,0,331,331,342,342,1,0,336,337,2,0, + 82,82,239,239,1,0,330,331,51,0,8,9,11,13,15,15,17,19,21,22,24,24, + 26,30,33,35,37,40,42,42,44,50,52,52,55,56,61,78,80,82,86,86,88,95, + 98,98,100,102,105,106,109,112,115,115,117,121,123,125,127,129,131, + 131,134,134,136,137,139,139,142,169,171,171,174,175,179,180,183, + 183,185,186,188,192,195,199,201,210,212,220,222,232,234,237,239, + 243,245,257,259,264,267,269,271,271,273,283,287,291,294,299,302, + 302,305,307,16,0,15,15,54,54,87,87,108,108,126,126,130,130,135,135, + 138,138,141,141,170,170,177,177,221,221,234,234,240,240,284,284, + 293,293,17,0,8,14,16,53,55,86,88,107,109,125,127,129,131,134,136, + 137,139,140,142,169,171,176,178,220,222,233,235,239,241,283,285, + 292,294,307,4068,0,350,1,0,0,0,2,359,1,0,0,0,4,362,1,0,0,0,6,365, + 1,0,0,0,8,368,1,0,0,0,10,371,1,0,0,0,12,374,1,0,0,0,14,1180,1,0, + 0,0,16,1184,1,0,0,0,18,1186,1,0,0,0,20,1188,1,0,0,0,22,1358,1,0, + 0,0,24,1360,1,0,0,0,26,1377,1,0,0,0,28,1383,1,0,0,0,30,1395,1,0, + 0,0,32,1408,1,0,0,0,34,1411,1,0,0,0,36,1415,1,0,0,0,38,1490,1,0, + 0,0,40,1492,1,0,0,0,42,1496,1,0,0,0,44,1517,1,0,0,0,46,1519,1,0, + 0,0,48,1521,1,0,0,0,50,1528,1,0,0,0,52,1530,1,0,0,0,54,1538,1,0, + 0,0,56,1547,1,0,0,0,58,1558,1,0,0,0,60,1576,1,0,0,0,62,1579,1,0, + 0,0,64,1590,1,0,0,0,66,1606,1,0,0,0,68,1612,1,0,0,0,70,1614,1,0, + 0,0,72,1625,1,0,0,0,74,1642,1,0,0,0,76,1650,1,0,0,0,78,1652,1,0, + 0,0,80,1658,1,0,0,0,82,1717,1,0,0,0,84,1729,1,0,0,0,86,1781,1,0, + 0,0,88,1784,1,0,0,0,90,1807,1,0,0,0,92,1809,1,0,0,0,94,1817,1,0, + 0,0,96,1850,1,0,0,0,98,1896,1,0,0,0,100,1917,1,0,0,0,102,1949,1, + 0,0,0,104,1961,1,0,0,0,106,1964,1,0,0,0,108,1973,1,0,0,0,110,1987, + 1,0,0,0,112,2006,1,0,0,0,114,2026,1,0,0,0,116,2032,1,0,0,0,118,2034, + 1,0,0,0,120,2042,1,0,0,0,122,2046,1,0,0,0,124,2049,1,0,0,0,126,2052, + 1,0,0,0,128,2078,1,0,0,0,130,2080,1,0,0,0,132,2115,1,0,0,0,134,2156, + 1,0,0,0,136,2160,1,0,0,0,138,2187,1,0,0,0,140,2191,1,0,0,0,142,2206, + 1,0,0,0,144,2208,1,0,0,0,146,2238,1,0,0,0,148,2240,1,0,0,0,150,2247, + 1,0,0,0,152,2260,1,0,0,0,154,2265,1,0,0,0,156,2267,1,0,0,0,158,2282, + 1,0,0,0,160,2306,1,0,0,0,162,2319,1,0,0,0,164,2321,1,0,0,0,166,2323, + 1,0,0,0,168,2327,1,0,0,0,170,2330,1,0,0,0,172,2334,1,0,0,0,174,2366, + 1,0,0,0,176,2369,1,0,0,0,178,2381,1,0,0,0,180,2400,1,0,0,0,182,2426, + 1,0,0,0,184,2432,1,0,0,0,186,2434,1,0,0,0,188,2470,1,0,0,0,190,2472, + 1,0,0,0,192,2476,1,0,0,0,194,2484,1,0,0,0,196,2495,1,0,0,0,198,2499, + 1,0,0,0,200,2510,1,0,0,0,202,2541,1,0,0,0,204,2543,1,0,0,0,206,2554, + 1,0,0,0,208,2576,1,0,0,0,210,2627,1,0,0,0,212,2629,1,0,0,0,214,2637, + 1,0,0,0,216,2645,1,0,0,0,218,2653,1,0,0,0,220,2661,1,0,0,0,222,2668, + 1,0,0,0,224,2672,1,0,0,0,226,2682,1,0,0,0,228,2690,1,0,0,0,230,2703, + 1,0,0,0,232,2718,1,0,0,0,234,2722,1,0,0,0,236,2724,1,0,0,0,238,2726, + 1,0,0,0,240,2746,1,0,0,0,242,2841,1,0,0,0,244,2847,1,0,0,0,246,2873, + 1,0,0,0,248,3113,1,0,0,0,250,3142,1,0,0,0,252,3144,1,0,0,0,254,3146, + 1,0,0,0,256,3148,1,0,0,0,258,3150,1,0,0,0,260,3152,1,0,0,0,262,3157, + 1,0,0,0,264,3164,1,0,0,0,266,3168,1,0,0,0,268,3173,1,0,0,0,270,3179, + 1,0,0,0,272,3186,1,0,0,0,274,3188,1,0,0,0,276,3193,1,0,0,0,278,3241, + 1,0,0,0,280,3243,1,0,0,0,282,3251,1,0,0,0,284,3264,1,0,0,0,286,3266, + 1,0,0,0,288,3269,1,0,0,0,290,3277,1,0,0,0,292,3286,1,0,0,0,294,3294, + 1,0,0,0,296,3307,1,0,0,0,298,3309,1,0,0,0,300,3316,1,0,0,0,302,3324, + 1,0,0,0,304,3336,1,0,0,0,306,3341,1,0,0,0,308,3350,1,0,0,0,310,3400, + 1,0,0,0,312,3418,1,0,0,0,314,3427,1,0,0,0,316,3429,1,0,0,0,318,3441, + 1,0,0,0,320,3443,1,0,0,0,322,3451,1,0,0,0,324,3461,1,0,0,0,326,3465, + 1,0,0,0,328,3471,1,0,0,0,330,3473,1,0,0,0,332,3475,1,0,0,0,334,3517, + 1,0,0,0,336,3530,1,0,0,0,338,3532,1,0,0,0,340,3536,1,0,0,0,342,3540, + 1,0,0,0,344,3542,1,0,0,0,346,3544,1,0,0,0,348,3546,1,0,0,0,350,354, + 3,14,7,0,351,353,5,1,0,0,352,351,1,0,0,0,353,356,1,0,0,0,354,352, + 1,0,0,0,354,355,1,0,0,0,355,357,1,0,0,0,356,354,1,0,0,0,357,358, + 5,0,0,1,358,1,1,0,0,0,359,360,3,224,112,0,360,361,5,0,0,1,361,3, + 1,0,0,0,362,363,3,220,110,0,363,364,5,0,0,1,364,5,1,0,0,0,365,366, + 3,214,107,0,366,367,5,0,0,1,367,7,1,0,0,0,368,369,3,222,111,0,369, + 370,5,0,0,1,370,9,1,0,0,0,371,372,3,278,139,0,372,373,5,0,0,1,373, + 11,1,0,0,0,374,375,3,288,144,0,375,376,5,0,0,1,376,13,1,0,0,0,377, + 1181,3,36,18,0,378,380,3,54,27,0,379,378,1,0,0,0,379,380,1,0,0,0, + 380,381,1,0,0,0,381,1181,3,82,41,0,382,383,5,291,0,0,383,1181,3, + 214,107,0,384,385,5,291,0,0,385,386,3,46,23,0,386,387,3,214,107, + 0,387,1181,1,0,0,0,388,389,5,239,0,0,389,392,5,33,0,0,390,393,3, + 326,163,0,391,393,3,338,169,0,392,390,1,0,0,0,392,391,1,0,0,0,393, + 1181,1,0,0,0,394,395,5,53,0,0,395,399,3,46,23,0,396,397,5,119,0, + 0,397,398,5,172,0,0,398,400,5,90,0,0,399,396,1,0,0,0,399,400,1,0, + 0,0,400,401,1,0,0,0,401,409,3,214,107,0,402,408,3,34,17,0,403,408, + 3,32,16,0,404,405,5,303,0,0,405,406,7,0,0,0,406,408,3,62,31,0,407, + 402,1,0,0,0,407,403,1,0,0,0,407,404,1,0,0,0,408,411,1,0,0,0,409, + 407,1,0,0,0,409,410,1,0,0,0,410,1181,1,0,0,0,411,409,1,0,0,0,412, + 413,5,11,0,0,413,414,3,46,23,0,414,415,3,214,107,0,415,416,5,239, + 0,0,416,417,7,0,0,0,417,418,3,62,31,0,418,1181,1,0,0,0,419,420,5, + 11,0,0,420,421,3,46,23,0,421,422,3,214,107,0,422,423,5,239,0,0,423, + 424,3,32,16,0,424,1181,1,0,0,0,425,426,5,82,0,0,426,429,3,46,23, + 0,427,428,5,119,0,0,428,430,5,90,0,0,429,427,1,0,0,0,429,430,1,0, + 0,0,430,431,1,0,0,0,431,433,3,214,107,0,432,434,7,1,0,0,433,432, + 1,0,0,0,433,434,1,0,0,0,434,1181,1,0,0,0,435,436,5,242,0,0,436,439, + 3,48,24,0,437,438,7,2,0,0,438,440,3,214,107,0,439,437,1,0,0,0,439, + 440,1,0,0,0,440,445,1,0,0,0,441,443,5,142,0,0,442,441,1,0,0,0,442, + 443,1,0,0,0,443,444,1,0,0,0,444,446,3,338,169,0,445,442,1,0,0,0, + 445,446,1,0,0,0,446,1181,1,0,0,0,447,452,3,24,12,0,448,449,5,2,0, + 0,449,450,3,292,146,0,450,451,5,3,0,0,451,453,1,0,0,0,452,448,1, + 0,0,0,452,453,1,0,0,0,453,455,1,0,0,0,454,456,3,58,29,0,455,454, + 1,0,0,0,455,456,1,0,0,0,456,457,1,0,0,0,457,462,3,60,30,0,458,460, + 5,20,0,0,459,458,1,0,0,0,459,460,1,0,0,0,460,461,1,0,0,0,461,463, + 3,36,18,0,462,459,1,0,0,0,462,463,1,0,0,0,463,1181,1,0,0,0,464,465, + 5,53,0,0,465,469,5,258,0,0,466,467,5,119,0,0,467,468,5,172,0,0,468, + 470,5,90,0,0,469,466,1,0,0,0,469,470,1,0,0,0,470,471,1,0,0,0,471, + 472,3,220,110,0,472,473,5,142,0,0,473,482,3,220,110,0,474,481,3, + 58,29,0,475,481,3,210,105,0,476,481,3,74,37,0,477,481,3,32,16,0, + 478,479,5,262,0,0,479,481,3,62,31,0,480,474,1,0,0,0,480,475,1,0, + 0,0,480,476,1,0,0,0,480,477,1,0,0,0,480,478,1,0,0,0,481,484,1,0, + 0,0,482,480,1,0,0,0,482,483,1,0,0,0,483,1181,1,0,0,0,484,482,1,0, + 0,0,485,490,3,26,13,0,486,487,5,2,0,0,487,488,3,292,146,0,488,489, + 5,3,0,0,489,491,1,0,0,0,490,486,1,0,0,0,490,491,1,0,0,0,491,493, + 1,0,0,0,492,494,3,58,29,0,493,492,1,0,0,0,493,494,1,0,0,0,494,495, + 1,0,0,0,495,500,3,60,30,0,496,498,5,20,0,0,497,496,1,0,0,0,497,498, + 1,0,0,0,498,499,1,0,0,0,499,501,3,36,18,0,500,497,1,0,0,0,500,501, + 1,0,0,0,501,1181,1,0,0,0,502,503,5,13,0,0,503,504,5,258,0,0,504, + 506,3,214,107,0,505,507,3,42,21,0,506,505,1,0,0,0,506,507,1,0,0, + 0,507,508,1,0,0,0,508,509,5,49,0,0,509,517,5,249,0,0,510,518,3,326, + 163,0,511,512,5,103,0,0,512,513,5,44,0,0,513,518,3,192,96,0,514, + 515,5,103,0,0,515,516,5,10,0,0,516,518,5,44,0,0,517,510,1,0,0,0, + 517,511,1,0,0,0,517,514,1,0,0,0,517,518,1,0,0,0,518,1181,1,0,0,0, + 519,520,5,13,0,0,520,523,5,259,0,0,521,522,7,2,0,0,522,524,3,214, + 107,0,523,521,1,0,0,0,523,524,1,0,0,0,524,525,1,0,0,0,525,526,5, + 49,0,0,526,528,5,249,0,0,527,529,3,326,163,0,528,527,1,0,0,0,528, + 529,1,0,0,0,529,1181,1,0,0,0,530,531,5,11,0,0,531,532,5,258,0,0, + 532,533,3,214,107,0,533,534,5,8,0,0,534,535,7,3,0,0,535,536,3,280, + 140,0,536,1181,1,0,0,0,537,538,5,11,0,0,538,539,5,258,0,0,539,540, + 3,214,107,0,540,541,5,8,0,0,541,542,7,3,0,0,542,543,5,2,0,0,543, + 544,3,280,140,0,544,545,5,3,0,0,545,1181,1,0,0,0,546,547,5,11,0, + 0,547,548,5,258,0,0,548,549,3,214,107,0,549,550,5,213,0,0,550,551, + 5,43,0,0,551,552,3,214,107,0,552,553,5,270,0,0,553,554,3,322,161, + 0,554,1181,1,0,0,0,555,556,5,11,0,0,556,557,5,258,0,0,557,558,3, + 214,107,0,558,559,5,82,0,0,559,562,7,3,0,0,560,561,5,119,0,0,561, + 563,5,90,0,0,562,560,1,0,0,0,562,563,1,0,0,0,563,564,1,0,0,0,564, + 565,5,2,0,0,565,566,3,212,106,0,566,567,5,3,0,0,567,1181,1,0,0,0, + 568,569,5,11,0,0,569,570,5,258,0,0,570,571,3,214,107,0,571,572,5, + 82,0,0,572,575,7,3,0,0,573,574,5,119,0,0,574,576,5,90,0,0,575,573, + 1,0,0,0,575,576,1,0,0,0,576,577,1,0,0,0,577,578,3,212,106,0,578, + 1181,1,0,0,0,579,580,5,11,0,0,580,581,7,4,0,0,581,582,3,214,107, + 0,582,583,5,213,0,0,583,584,5,270,0,0,584,585,3,214,107,0,585,1181, + 1,0,0,0,586,587,5,11,0,0,587,588,7,4,0,0,588,589,3,214,107,0,589, + 590,5,239,0,0,590,591,5,262,0,0,591,592,3,62,31,0,592,1181,1,0,0, + 0,593,594,5,11,0,0,594,595,7,4,0,0,595,596,3,214,107,0,596,597,5, + 289,0,0,597,600,5,262,0,0,598,599,5,119,0,0,599,601,5,90,0,0,600, + 598,1,0,0,0,600,601,1,0,0,0,601,602,1,0,0,0,602,603,3,62,31,0,603, + 1181,1,0,0,0,604,605,5,11,0,0,605,606,5,258,0,0,606,607,3,214,107, + 0,607,609,7,5,0,0,608,610,5,43,0,0,609,608,1,0,0,0,609,610,1,0,0, + 0,610,611,1,0,0,0,611,613,3,214,107,0,612,614,3,336,168,0,613,612, + 1,0,0,0,613,614,1,0,0,0,614,1181,1,0,0,0,615,616,5,11,0,0,616,617, + 5,258,0,0,617,619,3,214,107,0,618,620,3,42,21,0,619,618,1,0,0,0, + 619,620,1,0,0,0,620,621,1,0,0,0,621,623,5,35,0,0,622,624,5,43,0, + 0,623,622,1,0,0,0,623,624,1,0,0,0,624,625,1,0,0,0,625,626,3,214, + 107,0,626,628,3,290,145,0,627,629,3,276,138,0,628,627,1,0,0,0,628, + 629,1,0,0,0,629,1181,1,0,0,0,630,631,5,11,0,0,631,632,5,258,0,0, + 632,634,3,214,107,0,633,635,3,42,21,0,634,633,1,0,0,0,634,635,1, + 0,0,0,635,636,1,0,0,0,636,637,5,216,0,0,637,638,5,44,0,0,638,639, + 5,2,0,0,639,640,3,280,140,0,640,641,5,3,0,0,641,1181,1,0,0,0,642, + 643,5,11,0,0,643,644,5,258,0,0,644,646,3,214,107,0,645,647,3,42, + 21,0,646,645,1,0,0,0,646,647,1,0,0,0,647,648,1,0,0,0,648,649,5,239, + 0,0,649,650,5,236,0,0,650,654,3,338,169,0,651,652,5,303,0,0,652, + 653,5,237,0,0,653,655,3,62,31,0,654,651,1,0,0,0,654,655,1,0,0,0, + 655,1181,1,0,0,0,656,657,5,11,0,0,657,658,5,258,0,0,658,660,3,214, + 107,0,659,661,3,42,21,0,660,659,1,0,0,0,660,661,1,0,0,0,661,662, + 1,0,0,0,662,663,5,239,0,0,663,664,5,237,0,0,664,665,3,62,31,0,665, + 1181,1,0,0,0,666,667,5,11,0,0,667,668,7,4,0,0,668,669,3,214,107, + 0,669,673,5,8,0,0,670,671,5,119,0,0,671,672,5,172,0,0,672,674,5, + 90,0,0,673,670,1,0,0,0,673,674,1,0,0,0,674,676,1,0,0,0,675,677,3, + 40,20,0,676,675,1,0,0,0,677,678,1,0,0,0,678,676,1,0,0,0,678,679, + 1,0,0,0,679,1181,1,0,0,0,680,681,5,11,0,0,681,682,5,258,0,0,682, + 683,3,214,107,0,683,684,3,42,21,0,684,685,5,213,0,0,685,686,5,270, + 0,0,686,687,3,42,21,0,687,1181,1,0,0,0,688,689,5,11,0,0,689,690, + 7,4,0,0,690,691,3,214,107,0,691,694,5,82,0,0,692,693,5,119,0,0,693, + 695,5,90,0,0,694,692,1,0,0,0,694,695,1,0,0,0,695,696,1,0,0,0,696, + 701,3,42,21,0,697,698,5,4,0,0,698,700,3,42,21,0,699,697,1,0,0,0, + 700,703,1,0,0,0,701,699,1,0,0,0,701,702,1,0,0,0,702,705,1,0,0,0, + 703,701,1,0,0,0,704,706,5,203,0,0,705,704,1,0,0,0,705,706,1,0,0, + 0,706,1181,1,0,0,0,707,708,5,11,0,0,708,709,5,258,0,0,709,711,3, + 214,107,0,710,712,3,42,21,0,711,710,1,0,0,0,711,712,1,0,0,0,712, + 713,1,0,0,0,713,714,5,239,0,0,714,715,3,32,16,0,715,1181,1,0,0,0, + 716,717,5,11,0,0,717,718,5,258,0,0,718,719,3,214,107,0,719,720,5, + 209,0,0,720,721,5,192,0,0,721,1181,1,0,0,0,722,723,5,82,0,0,723, + 726,5,258,0,0,724,725,5,119,0,0,725,727,5,90,0,0,726,724,1,0,0,0, + 726,727,1,0,0,0,727,728,1,0,0,0,728,730,3,214,107,0,729,731,5,203, + 0,0,730,729,1,0,0,0,730,731,1,0,0,0,731,1181,1,0,0,0,732,733,5,82, + 0,0,733,736,5,296,0,0,734,735,5,119,0,0,735,737,5,90,0,0,736,734, + 1,0,0,0,736,737,1,0,0,0,737,738,1,0,0,0,738,1181,3,214,107,0,739, + 742,5,53,0,0,740,741,5,181,0,0,741,743,5,216,0,0,742,740,1,0,0,0, + 742,743,1,0,0,0,743,748,1,0,0,0,744,746,5,112,0,0,745,744,1,0,0, + 0,745,746,1,0,0,0,746,747,1,0,0,0,747,749,5,263,0,0,748,745,1,0, + 0,0,748,749,1,0,0,0,749,750,1,0,0,0,750,754,5,296,0,0,751,752,5, + 119,0,0,752,753,5,172,0,0,753,755,5,90,0,0,754,751,1,0,0,0,754,755, + 1,0,0,0,755,756,1,0,0,0,756,758,3,214,107,0,757,759,3,198,99,0,758, + 757,1,0,0,0,758,759,1,0,0,0,759,768,1,0,0,0,760,767,3,34,17,0,761, + 762,5,191,0,0,762,763,5,177,0,0,763,767,3,190,95,0,764,765,5,262, + 0,0,765,767,3,62,31,0,766,760,1,0,0,0,766,761,1,0,0,0,766,764,1, + 0,0,0,767,770,1,0,0,0,768,766,1,0,0,0,768,769,1,0,0,0,769,771,1, + 0,0,0,770,768,1,0,0,0,771,772,5,20,0,0,772,773,3,36,18,0,773,1181, + 1,0,0,0,774,777,5,53,0,0,775,776,5,181,0,0,776,778,5,216,0,0,777, + 775,1,0,0,0,777,778,1,0,0,0,778,780,1,0,0,0,779,781,5,112,0,0,780, + 779,1,0,0,0,780,781,1,0,0,0,781,782,1,0,0,0,782,783,5,263,0,0,783, + 784,5,296,0,0,784,789,3,220,110,0,785,786,5,2,0,0,786,787,3,288, + 144,0,787,788,5,3,0,0,788,790,1,0,0,0,789,785,1,0,0,0,789,790,1, + 0,0,0,790,791,1,0,0,0,791,794,3,58,29,0,792,793,5,180,0,0,793,795, + 3,62,31,0,794,792,1,0,0,0,794,795,1,0,0,0,795,1181,1,0,0,0,796,797, + 5,11,0,0,797,798,5,296,0,0,798,800,3,214,107,0,799,801,5,20,0,0, + 800,799,1,0,0,0,800,801,1,0,0,0,801,802,1,0,0,0,802,803,3,36,18, + 0,803,1181,1,0,0,0,804,807,5,53,0,0,805,806,5,181,0,0,806,808,5, + 216,0,0,807,805,1,0,0,0,807,808,1,0,0,0,808,810,1,0,0,0,809,811, + 5,263,0,0,810,809,1,0,0,0,810,811,1,0,0,0,811,812,1,0,0,0,812,816, + 5,109,0,0,813,814,5,119,0,0,814,815,5,172,0,0,815,817,5,90,0,0,816, + 813,1,0,0,0,816,817,1,0,0,0,817,818,1,0,0,0,818,819,3,214,107,0, + 819,820,5,20,0,0,820,830,3,338,169,0,821,822,5,293,0,0,822,827,3, + 80,40,0,823,824,5,4,0,0,824,826,3,80,40,0,825,823,1,0,0,0,826,829, + 1,0,0,0,827,825,1,0,0,0,827,828,1,0,0,0,828,831,1,0,0,0,829,827, + 1,0,0,0,830,821,1,0,0,0,830,831,1,0,0,0,831,1181,1,0,0,0,832,834, + 5,82,0,0,833,835,5,263,0,0,834,833,1,0,0,0,834,835,1,0,0,0,835,836, + 1,0,0,0,836,839,5,109,0,0,837,838,5,119,0,0,838,840,5,90,0,0,839, + 837,1,0,0,0,839,840,1,0,0,0,840,841,1,0,0,0,841,1181,3,214,107,0, + 842,844,5,91,0,0,843,845,7,6,0,0,844,843,1,0,0,0,844,845,1,0,0,0, + 845,846,1,0,0,0,846,1181,3,14,7,0,847,848,5,242,0,0,848,851,5,259, + 0,0,849,850,7,2,0,0,850,852,3,214,107,0,851,849,1,0,0,0,851,852, + 1,0,0,0,852,857,1,0,0,0,853,855,5,142,0,0,854,853,1,0,0,0,854,855, + 1,0,0,0,855,856,1,0,0,0,856,858,3,338,169,0,857,854,1,0,0,0,857, + 858,1,0,0,0,858,1181,1,0,0,0,859,860,5,242,0,0,860,861,5,258,0,0, + 861,864,5,93,0,0,862,863,7,2,0,0,863,865,3,214,107,0,864,862,1,0, + 0,0,864,865,1,0,0,0,865,866,1,0,0,0,866,867,5,142,0,0,867,869,3, + 338,169,0,868,870,3,42,21,0,869,868,1,0,0,0,869,870,1,0,0,0,870, + 1181,1,0,0,0,871,872,5,242,0,0,872,873,5,262,0,0,873,878,3,214,107, + 0,874,875,5,2,0,0,875,876,3,66,33,0,876,877,5,3,0,0,877,879,1,0, + 0,0,878,874,1,0,0,0,878,879,1,0,0,0,879,1181,1,0,0,0,880,881,5,242, + 0,0,881,882,5,44,0,0,882,883,7,2,0,0,883,886,3,214,107,0,884,885, + 7,2,0,0,885,887,3,214,107,0,886,884,1,0,0,0,886,887,1,0,0,0,887, + 1181,1,0,0,0,888,889,5,242,0,0,889,892,5,297,0,0,890,891,7,2,0,0, + 891,893,3,214,107,0,892,890,1,0,0,0,892,893,1,0,0,0,893,898,1,0, + 0,0,894,896,5,142,0,0,895,894,1,0,0,0,895,896,1,0,0,0,896,897,1, + 0,0,0,897,899,3,338,169,0,898,895,1,0,0,0,898,899,1,0,0,0,899,1181, + 1,0,0,0,900,901,5,242,0,0,901,902,5,192,0,0,902,904,3,214,107,0, + 903,905,3,42,21,0,904,903,1,0,0,0,904,905,1,0,0,0,905,1181,1,0,0, + 0,906,908,5,242,0,0,907,909,3,326,163,0,908,907,1,0,0,0,908,909, + 1,0,0,0,909,910,1,0,0,0,910,913,5,110,0,0,911,912,7,2,0,0,912,914, + 3,214,107,0,913,911,1,0,0,0,913,914,1,0,0,0,914,922,1,0,0,0,915, + 917,5,142,0,0,916,915,1,0,0,0,916,917,1,0,0,0,917,920,1,0,0,0,918, + 921,3,214,107,0,919,921,3,338,169,0,920,918,1,0,0,0,920,919,1,0, + 0,0,921,923,1,0,0,0,922,916,1,0,0,0,922,923,1,0,0,0,923,1181,1,0, + 0,0,924,925,5,242,0,0,925,926,5,53,0,0,926,927,5,258,0,0,927,930, + 3,214,107,0,928,929,5,20,0,0,929,931,5,236,0,0,930,928,1,0,0,0,930, + 931,1,0,0,0,931,1181,1,0,0,0,932,933,5,242,0,0,933,934,5,56,0,0, + 934,1181,3,46,23,0,935,936,5,242,0,0,936,941,5,34,0,0,937,939,5, + 142,0,0,938,937,1,0,0,0,938,939,1,0,0,0,939,940,1,0,0,0,940,942, + 3,338,169,0,941,938,1,0,0,0,941,942,1,0,0,0,942,1181,1,0,0,0,943, + 944,7,7,0,0,944,946,5,109,0,0,945,947,5,93,0,0,946,945,1,0,0,0,946, + 947,1,0,0,0,947,948,1,0,0,0,948,1181,3,50,25,0,949,950,7,7,0,0,950, + 952,3,46,23,0,951,953,5,93,0,0,952,951,1,0,0,0,952,953,1,0,0,0,953, + 954,1,0,0,0,954,955,3,214,107,0,955,1181,1,0,0,0,956,958,7,7,0,0, + 957,959,5,258,0,0,958,957,1,0,0,0,958,959,1,0,0,0,959,961,1,0,0, + 0,960,962,7,8,0,0,961,960,1,0,0,0,961,962,1,0,0,0,962,963,1,0,0, + 0,963,965,3,214,107,0,964,966,3,42,21,0,965,964,1,0,0,0,965,966, + 1,0,0,0,966,968,1,0,0,0,967,969,3,52,26,0,968,967,1,0,0,0,968,969, + 1,0,0,0,969,1181,1,0,0,0,970,972,7,7,0,0,971,973,5,205,0,0,972,971, + 1,0,0,0,972,973,1,0,0,0,973,974,1,0,0,0,974,1181,3,36,18,0,975,976, + 5,45,0,0,976,977,5,177,0,0,977,978,3,46,23,0,978,979,3,214,107,0, + 979,980,5,133,0,0,980,981,3,340,170,0,981,1181,1,0,0,0,982,983,5, + 45,0,0,983,984,5,177,0,0,984,985,5,258,0,0,985,986,3,214,107,0,986, + 987,5,133,0,0,987,988,3,340,170,0,988,1181,1,0,0,0,989,990,5,212, + 0,0,990,991,5,258,0,0,991,1181,3,214,107,0,992,993,5,212,0,0,993, + 994,5,109,0,0,994,1181,3,214,107,0,995,1003,5,212,0,0,996,1004,3, + 338,169,0,997,999,9,0,0,0,998,997,1,0,0,0,999,1002,1,0,0,0,1000, + 1001,1,0,0,0,1000,998,1,0,0,0,1001,1004,1,0,0,0,1002,1000,1,0,0, + 0,1003,996,1,0,0,0,1003,1000,1,0,0,0,1004,1181,1,0,0,0,1005,1007, + 5,29,0,0,1006,1008,5,139,0,0,1007,1006,1,0,0,0,1007,1008,1,0,0,0, + 1008,1009,1,0,0,0,1009,1010,5,258,0,0,1010,1013,3,214,107,0,1011, + 1012,5,180,0,0,1012,1014,3,62,31,0,1013,1011,1,0,0,0,1013,1014,1, + 0,0,0,1014,1019,1,0,0,0,1015,1017,5,20,0,0,1016,1015,1,0,0,0,1016, + 1017,1,0,0,0,1017,1018,1,0,0,0,1018,1020,3,36,18,0,1019,1016,1,0, + 0,0,1019,1020,1,0,0,0,1020,1181,1,0,0,0,1021,1022,5,283,0,0,1022, + 1025,5,258,0,0,1023,1024,5,119,0,0,1024,1026,5,90,0,0,1025,1023, + 1,0,0,0,1025,1026,1,0,0,0,1026,1027,1,0,0,0,1027,1181,3,214,107, + 0,1028,1029,5,37,0,0,1029,1181,5,29,0,0,1030,1031,5,147,0,0,1031, + 1033,5,64,0,0,1032,1034,5,148,0,0,1033,1032,1,0,0,0,1033,1034,1, + 0,0,0,1034,1035,1,0,0,0,1035,1036,5,127,0,0,1036,1038,3,338,169, + 0,1037,1039,5,189,0,0,1038,1037,1,0,0,0,1038,1039,1,0,0,0,1039,1040, + 1,0,0,0,1040,1041,5,132,0,0,1041,1042,5,258,0,0,1042,1044,3,214, + 107,0,1043,1045,3,42,21,0,1044,1043,1,0,0,0,1044,1045,1,0,0,0,1045, + 1181,1,0,0,0,1046,1047,5,278,0,0,1047,1048,5,258,0,0,1048,1050,3, + 214,107,0,1049,1051,3,42,21,0,1050,1049,1,0,0,0,1050,1051,1,0,0, + 0,1051,1181,1,0,0,0,1052,1054,5,165,0,0,1053,1052,1,0,0,0,1053,1054, + 1,0,0,0,1054,1055,1,0,0,0,1055,1056,5,214,0,0,1056,1057,5,258,0, + 0,1057,1060,3,214,107,0,1058,1059,7,9,0,0,1059,1061,5,192,0,0,1060, + 1058,1,0,0,0,1060,1061,1,0,0,0,1061,1181,1,0,0,0,1062,1063,7,10, + 0,0,1063,1067,3,326,163,0,1064,1066,9,0,0,0,1065,1064,1,0,0,0,1066, + 1069,1,0,0,0,1067,1068,1,0,0,0,1067,1065,1,0,0,0,1068,1181,1,0,0, + 0,1069,1067,1,0,0,0,1070,1071,5,239,0,0,1071,1075,5,223,0,0,1072, + 1074,9,0,0,0,1073,1072,1,0,0,0,1074,1077,1,0,0,0,1075,1076,1,0,0, + 0,1075,1073,1,0,0,0,1076,1181,1,0,0,0,1077,1075,1,0,0,0,1078,1079, + 5,239,0,0,1079,1080,5,266,0,0,1080,1081,5,307,0,0,1081,1181,3,260, + 130,0,1082,1083,5,239,0,0,1083,1084,5,266,0,0,1084,1085,5,307,0, + 0,1085,1181,3,16,8,0,1086,1087,5,239,0,0,1087,1088,5,266,0,0,1088, + 1092,5,307,0,0,1089,1091,9,0,0,0,1090,1089,1,0,0,0,1091,1094,1,0, + 0,0,1092,1093,1,0,0,0,1092,1090,1,0,0,0,1093,1181,1,0,0,0,1094,1092, + 1,0,0,0,1095,1096,5,239,0,0,1096,1097,3,18,9,0,1097,1098,5,308,0, + 0,1098,1099,3,20,10,0,1099,1181,1,0,0,0,1100,1101,5,239,0,0,1101, + 1109,3,18,9,0,1102,1106,5,308,0,0,1103,1105,9,0,0,0,1104,1103,1, + 0,0,0,1105,1108,1,0,0,0,1106,1107,1,0,0,0,1106,1104,1,0,0,0,1107, + 1110,1,0,0,0,1108,1106,1,0,0,0,1109,1102,1,0,0,0,1109,1110,1,0,0, + 0,1110,1181,1,0,0,0,1111,1115,5,239,0,0,1112,1114,9,0,0,0,1113,1112, + 1,0,0,0,1114,1117,1,0,0,0,1115,1116,1,0,0,0,1115,1113,1,0,0,0,1116, + 1118,1,0,0,0,1117,1115,1,0,0,0,1118,1119,5,308,0,0,1119,1181,3,20, + 10,0,1120,1124,5,239,0,0,1121,1123,9,0,0,0,1122,1121,1,0,0,0,1123, + 1126,1,0,0,0,1124,1125,1,0,0,0,1124,1122,1,0,0,0,1125,1181,1,0,0, + 0,1126,1124,1,0,0,0,1127,1128,5,217,0,0,1128,1181,3,18,9,0,1129, + 1133,5,217,0,0,1130,1132,9,0,0,0,1131,1130,1,0,0,0,1132,1135,1,0, + 0,0,1133,1134,1,0,0,0,1133,1131,1,0,0,0,1134,1181,1,0,0,0,1135,1133, + 1,0,0,0,1136,1137,5,53,0,0,1137,1141,5,124,0,0,1138,1139,5,119,0, + 0,1139,1140,5,172,0,0,1140,1142,5,90,0,0,1141,1138,1,0,0,0,1141, + 1142,1,0,0,0,1142,1143,1,0,0,0,1143,1144,3,326,163,0,1144,1146,5, + 177,0,0,1145,1147,5,258,0,0,1146,1145,1,0,0,0,1146,1147,1,0,0,0, + 1147,1148,1,0,0,0,1148,1151,3,214,107,0,1149,1150,5,293,0,0,1150, + 1152,3,326,163,0,1151,1149,1,0,0,0,1151,1152,1,0,0,0,1152,1153,1, + 0,0,0,1153,1154,5,2,0,0,1154,1155,3,216,108,0,1155,1158,5,3,0,0, + 1156,1157,5,180,0,0,1157,1159,3,62,31,0,1158,1156,1,0,0,0,1158,1159, + 1,0,0,0,1159,1181,1,0,0,0,1160,1161,5,82,0,0,1161,1164,5,124,0,0, + 1162,1163,5,119,0,0,1163,1165,5,90,0,0,1164,1162,1,0,0,0,1164,1165, + 1,0,0,0,1165,1166,1,0,0,0,1166,1167,3,326,163,0,1167,1169,5,177, + 0,0,1168,1170,5,258,0,0,1169,1168,1,0,0,0,1169,1170,1,0,0,0,1170, + 1171,1,0,0,0,1171,1172,3,214,107,0,1172,1181,1,0,0,0,1173,1177,3, + 22,11,0,1174,1176,9,0,0,0,1175,1174,1,0,0,0,1176,1179,1,0,0,0,1177, + 1178,1,0,0,0,1177,1175,1,0,0,0,1178,1181,1,0,0,0,1179,1177,1,0,0, + 0,1180,377,1,0,0,0,1180,379,1,0,0,0,1180,382,1,0,0,0,1180,384,1, + 0,0,0,1180,388,1,0,0,0,1180,394,1,0,0,0,1180,412,1,0,0,0,1180,419, + 1,0,0,0,1180,425,1,0,0,0,1180,435,1,0,0,0,1180,447,1,0,0,0,1180, + 464,1,0,0,0,1180,485,1,0,0,0,1180,502,1,0,0,0,1180,519,1,0,0,0,1180, + 530,1,0,0,0,1180,537,1,0,0,0,1180,546,1,0,0,0,1180,555,1,0,0,0,1180, + 568,1,0,0,0,1180,579,1,0,0,0,1180,586,1,0,0,0,1180,593,1,0,0,0,1180, + 604,1,0,0,0,1180,615,1,0,0,0,1180,630,1,0,0,0,1180,642,1,0,0,0,1180, + 656,1,0,0,0,1180,666,1,0,0,0,1180,680,1,0,0,0,1180,688,1,0,0,0,1180, + 707,1,0,0,0,1180,716,1,0,0,0,1180,722,1,0,0,0,1180,732,1,0,0,0,1180, + 739,1,0,0,0,1180,774,1,0,0,0,1180,796,1,0,0,0,1180,804,1,0,0,0,1180, + 832,1,0,0,0,1180,842,1,0,0,0,1180,847,1,0,0,0,1180,859,1,0,0,0,1180, + 871,1,0,0,0,1180,880,1,0,0,0,1180,888,1,0,0,0,1180,900,1,0,0,0,1180, + 906,1,0,0,0,1180,924,1,0,0,0,1180,932,1,0,0,0,1180,935,1,0,0,0,1180, + 943,1,0,0,0,1180,949,1,0,0,0,1180,956,1,0,0,0,1180,970,1,0,0,0,1180, + 975,1,0,0,0,1180,982,1,0,0,0,1180,989,1,0,0,0,1180,992,1,0,0,0,1180, + 995,1,0,0,0,1180,1005,1,0,0,0,1180,1021,1,0,0,0,1180,1028,1,0,0, + 0,1180,1030,1,0,0,0,1180,1046,1,0,0,0,1180,1053,1,0,0,0,1180,1062, + 1,0,0,0,1180,1070,1,0,0,0,1180,1078,1,0,0,0,1180,1082,1,0,0,0,1180, + 1086,1,0,0,0,1180,1095,1,0,0,0,1180,1100,1,0,0,0,1180,1111,1,0,0, + 0,1180,1120,1,0,0,0,1180,1127,1,0,0,0,1180,1129,1,0,0,0,1180,1136, + 1,0,0,0,1180,1160,1,0,0,0,1180,1173,1,0,0,0,1181,15,1,0,0,0,1182, + 1185,3,338,169,0,1183,1185,5,148,0,0,1184,1182,1,0,0,0,1184,1183, + 1,0,0,0,1185,17,1,0,0,0,1186,1187,3,330,165,0,1187,19,1,0,0,0,1188, + 1189,3,332,166,0,1189,21,1,0,0,0,1190,1191,5,53,0,0,1191,1359,5, + 223,0,0,1192,1193,5,82,0,0,1193,1359,5,223,0,0,1194,1196,5,113,0, + 0,1195,1197,5,223,0,0,1196,1195,1,0,0,0,1196,1197,1,0,0,0,1197,1359, + 1,0,0,0,1198,1200,5,220,0,0,1199,1201,5,223,0,0,1200,1199,1,0,0, + 0,1200,1201,1,0,0,0,1201,1359,1,0,0,0,1202,1203,5,242,0,0,1203,1359, + 5,113,0,0,1204,1205,5,242,0,0,1205,1207,5,223,0,0,1206,1208,5,113, + 0,0,1207,1206,1,0,0,0,1207,1208,1,0,0,0,1208,1359,1,0,0,0,1209,1210, + 5,242,0,0,1210,1359,5,201,0,0,1211,1212,5,242,0,0,1212,1359,5,224, + 0,0,1213,1214,5,242,0,0,1214,1215,5,56,0,0,1215,1359,5,224,0,0,1216, + 1217,5,92,0,0,1217,1359,5,258,0,0,1218,1219,5,121,0,0,1219,1359, + 5,258,0,0,1220,1221,5,242,0,0,1221,1359,5,48,0,0,1222,1223,5,242, + 0,0,1223,1224,5,53,0,0,1224,1359,5,258,0,0,1225,1226,5,242,0,0,1226, + 1359,5,274,0,0,1227,1228,5,242,0,0,1228,1359,5,125,0,0,1229,1230, + 5,242,0,0,1230,1359,5,151,0,0,1231,1232,5,53,0,0,1232,1359,5,124, + 0,0,1233,1234,5,82,0,0,1234,1359,5,124,0,0,1235,1236,5,11,0,0,1236, + 1359,5,124,0,0,1237,1238,5,150,0,0,1238,1359,5,258,0,0,1239,1240, + 5,150,0,0,1240,1359,5,65,0,0,1241,1242,5,287,0,0,1242,1359,5,258, + 0,0,1243,1244,5,287,0,0,1244,1359,5,65,0,0,1245,1246,5,53,0,0,1246, + 1247,5,263,0,0,1247,1359,5,153,0,0,1248,1249,5,82,0,0,1249,1250, + 5,263,0,0,1250,1359,5,153,0,0,1251,1252,5,11,0,0,1252,1253,5,258, + 0,0,1253,1254,3,220,110,0,1254,1255,5,172,0,0,1255,1256,5,39,0,0, + 1256,1359,1,0,0,0,1257,1258,5,11,0,0,1258,1259,5,258,0,0,1259,1260, + 3,220,110,0,1260,1261,5,39,0,0,1261,1262,5,28,0,0,1262,1359,1,0, + 0,0,1263,1264,5,11,0,0,1264,1265,5,258,0,0,1265,1266,3,220,110,0, + 1266,1267,5,172,0,0,1267,1268,5,246,0,0,1268,1359,1,0,0,0,1269,1270, + 5,11,0,0,1270,1271,5,258,0,0,1271,1272,3,220,110,0,1272,1273,5,243, + 0,0,1273,1274,5,28,0,0,1274,1359,1,0,0,0,1275,1276,5,11,0,0,1276, + 1277,5,258,0,0,1277,1278,3,220,110,0,1278,1279,5,172,0,0,1279,1280, + 5,243,0,0,1280,1359,1,0,0,0,1281,1282,5,11,0,0,1282,1283,5,258,0, + 0,1283,1284,3,220,110,0,1284,1285,5,172,0,0,1285,1286,5,250,0,0, + 1286,1287,5,20,0,0,1287,1288,5,77,0,0,1288,1359,1,0,0,0,1289,1290, + 5,11,0,0,1290,1291,5,258,0,0,1291,1292,3,220,110,0,1292,1293,5,239, + 0,0,1293,1294,5,243,0,0,1294,1295,5,149,0,0,1295,1359,1,0,0,0,1296, + 1297,5,11,0,0,1297,1298,5,258,0,0,1298,1299,3,220,110,0,1299,1300, + 5,88,0,0,1300,1301,5,190,0,0,1301,1359,1,0,0,0,1302,1303,5,11,0, + 0,1303,1304,5,258,0,0,1304,1305,3,220,110,0,1305,1306,5,18,0,0,1306, + 1307,5,190,0,0,1307,1359,1,0,0,0,1308,1309,5,11,0,0,1309,1310,5, + 258,0,0,1310,1311,3,220,110,0,1311,1312,5,281,0,0,1312,1313,5,190, + 0,0,1313,1359,1,0,0,0,1314,1315,5,11,0,0,1315,1316,5,258,0,0,1316, + 1317,3,220,110,0,1317,1318,5,271,0,0,1318,1359,1,0,0,0,1319,1320, + 5,11,0,0,1320,1321,5,258,0,0,1321,1323,3,220,110,0,1322,1324,3,42, + 21,0,1323,1322,1,0,0,0,1323,1324,1,0,0,0,1324,1325,1,0,0,0,1325, + 1326,5,47,0,0,1326,1359,1,0,0,0,1327,1328,5,11,0,0,1328,1329,5,258, + 0,0,1329,1331,3,220,110,0,1330,1332,3,42,21,0,1331,1330,1,0,0,0, + 1331,1332,1,0,0,0,1332,1333,1,0,0,0,1333,1334,5,50,0,0,1334,1359, + 1,0,0,0,1335,1336,5,11,0,0,1336,1337,5,258,0,0,1337,1339,3,220,110, + 0,1338,1340,3,42,21,0,1339,1338,1,0,0,0,1339,1340,1,0,0,0,1340,1341, + 1,0,0,0,1341,1342,5,239,0,0,1342,1343,5,100,0,0,1343,1359,1,0,0, + 0,1344,1345,5,11,0,0,1345,1346,5,258,0,0,1346,1348,3,220,110,0,1347, + 1349,3,42,21,0,1348,1347,1,0,0,0,1348,1349,1,0,0,0,1349,1350,1,0, + 0,0,1350,1351,5,216,0,0,1351,1352,5,44,0,0,1352,1359,1,0,0,0,1353, + 1354,5,248,0,0,1354,1359,5,273,0,0,1355,1359,5,46,0,0,1356,1359, + 5,225,0,0,1357,1359,5,76,0,0,1358,1190,1,0,0,0,1358,1192,1,0,0,0, + 1358,1194,1,0,0,0,1358,1198,1,0,0,0,1358,1202,1,0,0,0,1358,1204, + 1,0,0,0,1358,1209,1,0,0,0,1358,1211,1,0,0,0,1358,1213,1,0,0,0,1358, + 1216,1,0,0,0,1358,1218,1,0,0,0,1358,1220,1,0,0,0,1358,1222,1,0,0, + 0,1358,1225,1,0,0,0,1358,1227,1,0,0,0,1358,1229,1,0,0,0,1358,1231, + 1,0,0,0,1358,1233,1,0,0,0,1358,1235,1,0,0,0,1358,1237,1,0,0,0,1358, + 1239,1,0,0,0,1358,1241,1,0,0,0,1358,1243,1,0,0,0,1358,1245,1,0,0, + 0,1358,1248,1,0,0,0,1358,1251,1,0,0,0,1358,1257,1,0,0,0,1358,1263, + 1,0,0,0,1358,1269,1,0,0,0,1358,1275,1,0,0,0,1358,1281,1,0,0,0,1358, + 1289,1,0,0,0,1358,1296,1,0,0,0,1358,1302,1,0,0,0,1358,1308,1,0,0, + 0,1358,1314,1,0,0,0,1358,1319,1,0,0,0,1358,1327,1,0,0,0,1358,1335, + 1,0,0,0,1358,1344,1,0,0,0,1358,1353,1,0,0,0,1358,1355,1,0,0,0,1358, + 1356,1,0,0,0,1358,1357,1,0,0,0,1359,23,1,0,0,0,1360,1362,5,53,0, + 0,1361,1363,5,263,0,0,1362,1361,1,0,0,0,1362,1363,1,0,0,0,1363,1365, + 1,0,0,0,1364,1366,5,94,0,0,1365,1364,1,0,0,0,1365,1366,1,0,0,0,1366, + 1367,1,0,0,0,1367,1371,5,258,0,0,1368,1369,5,119,0,0,1369,1370,5, + 172,0,0,1370,1372,5,90,0,0,1371,1368,1,0,0,0,1371,1372,1,0,0,0,1372, + 1373,1,0,0,0,1373,1374,3,214,107,0,1374,25,1,0,0,0,1375,1376,5,53, + 0,0,1376,1378,5,181,0,0,1377,1375,1,0,0,0,1377,1378,1,0,0,0,1378, + 1379,1,0,0,0,1379,1380,5,216,0,0,1380,1381,5,258,0,0,1381,1382,3, + 214,107,0,1382,27,1,0,0,0,1383,1384,5,39,0,0,1384,1385,5,28,0,0, + 1385,1389,3,190,95,0,1386,1387,5,246,0,0,1387,1388,5,28,0,0,1388, + 1390,3,194,97,0,1389,1386,1,0,0,0,1389,1390,1,0,0,0,1390,1391,1, + 0,0,0,1391,1392,5,132,0,0,1392,1393,5,335,0,0,1393,1394,5,27,0,0, + 1394,29,1,0,0,0,1395,1396,5,243,0,0,1396,1397,5,28,0,0,1397,1398, + 3,190,95,0,1398,1401,5,177,0,0,1399,1402,3,70,35,0,1400,1402,3,72, + 36,0,1401,1399,1,0,0,0,1401,1400,1,0,0,0,1402,1406,1,0,0,0,1403, + 1404,5,250,0,0,1404,1405,5,20,0,0,1405,1407,5,77,0,0,1406,1403,1, + 0,0,0,1406,1407,1,0,0,0,1407,31,1,0,0,0,1408,1409,5,149,0,0,1409, + 1410,3,338,169,0,1410,33,1,0,0,0,1411,1412,5,45,0,0,1412,1413,3, + 338,169,0,1413,35,1,0,0,0,1414,1416,3,54,27,0,1415,1414,1,0,0,0, + 1415,1416,1,0,0,0,1416,1417,1,0,0,0,1417,1418,3,88,44,0,1418,1419, + 3,84,42,0,1419,37,1,0,0,0,1420,1421,5,129,0,0,1421,1423,5,189,0, + 0,1422,1424,5,258,0,0,1423,1422,1,0,0,0,1423,1424,1,0,0,0,1424,1425, + 1,0,0,0,1425,1432,3,214,107,0,1426,1430,3,42,21,0,1427,1428,5,119, + 0,0,1428,1429,5,172,0,0,1429,1431,5,90,0,0,1430,1427,1,0,0,0,1430, + 1431,1,0,0,0,1431,1433,1,0,0,0,1432,1426,1,0,0,0,1432,1433,1,0,0, + 0,1433,1435,1,0,0,0,1434,1436,3,190,95,0,1435,1434,1,0,0,0,1435, + 1436,1,0,0,0,1436,1491,1,0,0,0,1437,1438,5,129,0,0,1438,1440,5,132, + 0,0,1439,1441,5,258,0,0,1440,1439,1,0,0,0,1440,1441,1,0,0,0,1441, + 1442,1,0,0,0,1442,1444,3,214,107,0,1443,1445,3,42,21,0,1444,1443, + 1,0,0,0,1444,1445,1,0,0,0,1445,1449,1,0,0,0,1446,1447,5,119,0,0, + 1447,1448,5,172,0,0,1448,1450,5,90,0,0,1449,1446,1,0,0,0,1449,1450, + 1,0,0,0,1450,1452,1,0,0,0,1451,1453,3,190,95,0,1452,1451,1,0,0,0, + 1452,1453,1,0,0,0,1453,1491,1,0,0,0,1454,1455,5,129,0,0,1455,1457, + 5,132,0,0,1456,1458,5,258,0,0,1457,1456,1,0,0,0,1457,1458,1,0,0, + 0,1458,1459,1,0,0,0,1459,1460,3,214,107,0,1460,1461,5,216,0,0,1461, + 1462,3,122,61,0,1462,1491,1,0,0,0,1463,1464,5,129,0,0,1464,1466, + 5,189,0,0,1465,1467,5,148,0,0,1466,1465,1,0,0,0,1466,1467,1,0,0, + 0,1467,1468,1,0,0,0,1468,1469,5,78,0,0,1469,1471,3,338,169,0,1470, + 1472,3,210,105,0,1471,1470,1,0,0,0,1471,1472,1,0,0,0,1472,1474,1, + 0,0,0,1473,1475,3,74,37,0,1474,1473,1,0,0,0,1474,1475,1,0,0,0,1475, + 1491,1,0,0,0,1476,1477,5,129,0,0,1477,1479,5,189,0,0,1478,1480,5, + 148,0,0,1479,1478,1,0,0,0,1479,1480,1,0,0,0,1480,1481,1,0,0,0,1481, + 1483,5,78,0,0,1482,1484,3,338,169,0,1483,1482,1,0,0,0,1483,1484, + 1,0,0,0,1484,1485,1,0,0,0,1485,1488,3,58,29,0,1486,1487,5,180,0, + 0,1487,1489,3,62,31,0,1488,1486,1,0,0,0,1488,1489,1,0,0,0,1489,1491, + 1,0,0,0,1490,1420,1,0,0,0,1490,1437,1,0,0,0,1490,1454,1,0,0,0,1490, + 1463,1,0,0,0,1490,1476,1,0,0,0,1491,39,1,0,0,0,1492,1494,3,42,21, + 0,1493,1495,3,32,16,0,1494,1493,1,0,0,0,1494,1495,1,0,0,0,1495,41, + 1,0,0,0,1496,1497,5,190,0,0,1497,1498,5,2,0,0,1498,1503,3,44,22, + 0,1499,1500,5,4,0,0,1500,1502,3,44,22,0,1501,1499,1,0,0,0,1502,1505, + 1,0,0,0,1503,1501,1,0,0,0,1503,1504,1,0,0,0,1504,1506,1,0,0,0,1505, + 1503,1,0,0,0,1506,1507,5,3,0,0,1507,43,1,0,0,0,1508,1511,3,326,163, + 0,1509,1510,5,308,0,0,1510,1512,3,250,125,0,1511,1509,1,0,0,0,1511, + 1512,1,0,0,0,1512,1518,1,0,0,0,1513,1514,3,326,163,0,1514,1515,5, + 308,0,0,1515,1516,5,70,0,0,1516,1518,1,0,0,0,1517,1508,1,0,0,0,1517, + 1513,1,0,0,0,1518,45,1,0,0,0,1519,1520,7,11,0,0,1520,47,1,0,0,0, + 1521,1522,7,12,0,0,1522,49,1,0,0,0,1523,1529,3,320,160,0,1524,1529, + 3,338,169,0,1525,1529,3,252,126,0,1526,1529,3,254,127,0,1527,1529, + 3,256,128,0,1528,1523,1,0,0,0,1528,1524,1,0,0,0,1528,1525,1,0,0, + 0,1528,1526,1,0,0,0,1528,1527,1,0,0,0,1529,51,1,0,0,0,1530,1535, + 3,326,163,0,1531,1532,5,5,0,0,1532,1534,3,326,163,0,1533,1531,1, + 0,0,0,1534,1537,1,0,0,0,1535,1533,1,0,0,0,1535,1536,1,0,0,0,1536, + 53,1,0,0,0,1537,1535,1,0,0,0,1538,1539,5,303,0,0,1539,1544,3,56, + 28,0,1540,1541,5,4,0,0,1541,1543,3,56,28,0,1542,1540,1,0,0,0,1543, + 1546,1,0,0,0,1544,1542,1,0,0,0,1544,1545,1,0,0,0,1545,55,1,0,0,0, + 1546,1544,1,0,0,0,1547,1549,3,322,161,0,1548,1550,3,190,95,0,1549, + 1548,1,0,0,0,1549,1550,1,0,0,0,1550,1552,1,0,0,0,1551,1553,5,20, + 0,0,1552,1551,1,0,0,0,1552,1553,1,0,0,0,1553,1554,1,0,0,0,1554,1555, + 5,2,0,0,1555,1556,3,36,18,0,1556,1557,5,3,0,0,1557,57,1,0,0,0,1558, + 1559,5,293,0,0,1559,1560,3,214,107,0,1560,59,1,0,0,0,1561,1562,5, + 180,0,0,1562,1575,3,62,31,0,1563,1564,5,191,0,0,1564,1565,5,28,0, + 0,1565,1575,3,228,114,0,1566,1575,3,30,15,0,1567,1575,3,28,14,0, + 1568,1575,3,210,105,0,1569,1575,3,74,37,0,1570,1575,3,32,16,0,1571, + 1575,3,34,17,0,1572,1573,5,262,0,0,1573,1575,3,62,31,0,1574,1561, + 1,0,0,0,1574,1563,1,0,0,0,1574,1566,1,0,0,0,1574,1567,1,0,0,0,1574, + 1568,1,0,0,0,1574,1569,1,0,0,0,1574,1570,1,0,0,0,1574,1571,1,0,0, + 0,1574,1572,1,0,0,0,1575,1578,1,0,0,0,1576,1574,1,0,0,0,1576,1577, + 1,0,0,0,1577,61,1,0,0,0,1578,1576,1,0,0,0,1579,1580,5,2,0,0,1580, + 1585,3,64,32,0,1581,1582,5,4,0,0,1582,1584,3,64,32,0,1583,1581,1, + 0,0,0,1584,1587,1,0,0,0,1585,1583,1,0,0,0,1585,1586,1,0,0,0,1586, + 1588,1,0,0,0,1587,1585,1,0,0,0,1588,1589,5,3,0,0,1589,63,1,0,0,0, + 1590,1595,3,66,33,0,1591,1593,5,308,0,0,1592,1591,1,0,0,0,1592,1593, + 1,0,0,0,1593,1594,1,0,0,0,1594,1596,3,68,34,0,1595,1592,1,0,0,0, + 1595,1596,1,0,0,0,1596,65,1,0,0,0,1597,1602,3,326,163,0,1598,1599, + 5,5,0,0,1599,1601,3,326,163,0,1600,1598,1,0,0,0,1601,1604,1,0,0, + 0,1602,1600,1,0,0,0,1602,1603,1,0,0,0,1603,1607,1,0,0,0,1604,1602, + 1,0,0,0,1605,1607,3,338,169,0,1606,1597,1,0,0,0,1606,1605,1,0,0, + 0,1607,67,1,0,0,0,1608,1613,5,335,0,0,1609,1613,5,337,0,0,1610,1613, + 3,258,129,0,1611,1613,3,338,169,0,1612,1608,1,0,0,0,1612,1609,1, + 0,0,0,1612,1610,1,0,0,0,1612,1611,1,0,0,0,1613,69,1,0,0,0,1614,1615, + 5,2,0,0,1615,1620,3,250,125,0,1616,1617,5,4,0,0,1617,1619,3,250, + 125,0,1618,1616,1,0,0,0,1619,1622,1,0,0,0,1620,1618,1,0,0,0,1620, + 1621,1,0,0,0,1621,1623,1,0,0,0,1622,1620,1,0,0,0,1623,1624,5,3,0, + 0,1624,71,1,0,0,0,1625,1626,5,2,0,0,1626,1631,3,70,35,0,1627,1628, + 5,4,0,0,1628,1630,3,70,35,0,1629,1627,1,0,0,0,1630,1633,1,0,0,0, + 1631,1629,1,0,0,0,1631,1632,1,0,0,0,1632,1634,1,0,0,0,1633,1631, + 1,0,0,0,1634,1635,5,3,0,0,1635,73,1,0,0,0,1636,1637,5,250,0,0,1637, + 1638,5,20,0,0,1638,1643,3,76,38,0,1639,1640,5,250,0,0,1640,1641, + 5,28,0,0,1641,1643,3,78,39,0,1642,1636,1,0,0,0,1642,1639,1,0,0,0, + 1643,75,1,0,0,0,1644,1645,5,128,0,0,1645,1646,3,338,169,0,1646,1647, + 5,185,0,0,1647,1648,3,338,169,0,1648,1651,1,0,0,0,1649,1651,3,326, + 163,0,1650,1644,1,0,0,0,1650,1649,1,0,0,0,1651,77,1,0,0,0,1652,1656, + 3,338,169,0,1653,1654,5,303,0,0,1654,1655,5,237,0,0,1655,1657,3, + 62,31,0,1656,1653,1,0,0,0,1656,1657,1,0,0,0,1657,79,1,0,0,0,1658, + 1659,3,326,163,0,1659,1660,3,338,169,0,1660,81,1,0,0,0,1661,1662, + 3,38,19,0,1662,1663,3,36,18,0,1663,1718,1,0,0,0,1664,1666,3,130, + 65,0,1665,1667,3,86,43,0,1666,1665,1,0,0,0,1667,1668,1,0,0,0,1668, + 1666,1,0,0,0,1668,1669,1,0,0,0,1669,1718,1,0,0,0,1670,1671,5,72, + 0,0,1671,1672,5,107,0,0,1672,1673,3,214,107,0,1673,1675,3,208,104, + 0,1674,1676,3,122,61,0,1675,1674,1,0,0,0,1675,1676,1,0,0,0,1676, + 1718,1,0,0,0,1677,1678,5,290,0,0,1678,1679,3,214,107,0,1679,1680, + 3,208,104,0,1680,1682,3,104,52,0,1681,1683,3,122,61,0,1682,1681, + 1,0,0,0,1682,1683,1,0,0,0,1683,1718,1,0,0,0,1684,1685,5,156,0,0, + 1685,1686,5,132,0,0,1686,1687,3,214,107,0,1687,1688,3,208,104,0, + 1688,1694,5,293,0,0,1689,1695,3,214,107,0,1690,1691,5,2,0,0,1691, + 1692,3,36,18,0,1692,1693,5,3,0,0,1693,1695,1,0,0,0,1694,1689,1,0, + 0,0,1694,1690,1,0,0,0,1695,1696,1,0,0,0,1696,1697,3,208,104,0,1697, + 1698,5,177,0,0,1698,1702,3,240,120,0,1699,1701,3,106,53,0,1700,1699, + 1,0,0,0,1701,1704,1,0,0,0,1702,1700,1,0,0,0,1702,1703,1,0,0,0,1703, + 1708,1,0,0,0,1704,1702,1,0,0,0,1705,1707,3,108,54,0,1706,1705,1, + 0,0,0,1707,1710,1,0,0,0,1708,1706,1,0,0,0,1708,1709,1,0,0,0,1709, + 1714,1,0,0,0,1710,1708,1,0,0,0,1711,1713,3,110,55,0,1712,1711,1, + 0,0,0,1713,1716,1,0,0,0,1714,1712,1,0,0,0,1714,1715,1,0,0,0,1715, + 1718,1,0,0,0,1716,1714,1,0,0,0,1717,1661,1,0,0,0,1717,1664,1,0,0, + 0,1717,1670,1,0,0,0,1717,1677,1,0,0,0,1717,1684,1,0,0,0,1718,83, + 1,0,0,0,1719,1720,5,182,0,0,1720,1721,5,28,0,0,1721,1726,3,92,46, + 0,1722,1723,5,4,0,0,1723,1725,3,92,46,0,1724,1722,1,0,0,0,1725,1728, + 1,0,0,0,1726,1724,1,0,0,0,1726,1727,1,0,0,0,1727,1730,1,0,0,0,1728, + 1726,1,0,0,0,1729,1719,1,0,0,0,1729,1730,1,0,0,0,1730,1741,1,0,0, + 0,1731,1732,5,38,0,0,1732,1733,5,28,0,0,1733,1738,3,236,118,0,1734, + 1735,5,4,0,0,1735,1737,3,236,118,0,1736,1734,1,0,0,0,1737,1740,1, + 0,0,0,1738,1736,1,0,0,0,1738,1739,1,0,0,0,1739,1742,1,0,0,0,1740, + 1738,1,0,0,0,1741,1731,1,0,0,0,1741,1742,1,0,0,0,1742,1753,1,0,0, + 0,1743,1744,5,80,0,0,1744,1745,5,28,0,0,1745,1750,3,236,118,0,1746, + 1747,5,4,0,0,1747,1749,3,236,118,0,1748,1746,1,0,0,0,1749,1752,1, + 0,0,0,1750,1748,1,0,0,0,1750,1751,1,0,0,0,1751,1754,1,0,0,0,1752, + 1750,1,0,0,0,1753,1743,1,0,0,0,1753,1754,1,0,0,0,1754,1765,1,0,0, + 0,1755,1756,5,245,0,0,1756,1757,5,28,0,0,1757,1762,3,92,46,0,1758, + 1759,5,4,0,0,1759,1761,3,92,46,0,1760,1758,1,0,0,0,1761,1764,1,0, + 0,0,1762,1760,1,0,0,0,1762,1763,1,0,0,0,1763,1766,1,0,0,0,1764,1762, + 1,0,0,0,1765,1755,1,0,0,0,1765,1766,1,0,0,0,1766,1768,1,0,0,0,1767, + 1769,3,306,153,0,1768,1767,1,0,0,0,1768,1769,1,0,0,0,1769,1775,1, + 0,0,0,1770,1773,5,144,0,0,1771,1774,5,10,0,0,1772,1774,3,236,118, + 0,1773,1771,1,0,0,0,1773,1772,1,0,0,0,1774,1776,1,0,0,0,1775,1770, + 1,0,0,0,1775,1776,1,0,0,0,1776,1779,1,0,0,0,1777,1778,5,176,0,0, + 1778,1780,3,236,118,0,1779,1777,1,0,0,0,1779,1780,1,0,0,0,1780,85, + 1,0,0,0,1781,1782,3,38,19,0,1782,1783,3,96,48,0,1783,87,1,0,0,0, + 1784,1785,6,44,-1,0,1785,1786,3,90,45,0,1786,1795,1,0,0,0,1787,1788, + 10,1,0,0,1788,1790,7,13,0,0,1789,1791,3,174,87,0,1790,1789,1,0,0, + 0,1790,1791,1,0,0,0,1791,1792,1,0,0,0,1792,1794,3,88,44,2,1793,1787, + 1,0,0,0,1794,1797,1,0,0,0,1795,1793,1,0,0,0,1795,1796,1,0,0,0,1796, + 89,1,0,0,0,1797,1795,1,0,0,0,1798,1808,3,98,49,0,1799,1808,3,94, + 47,0,1800,1801,5,258,0,0,1801,1808,3,214,107,0,1802,1808,3,204,102, + 0,1803,1804,5,2,0,0,1804,1805,3,36,18,0,1805,1806,5,3,0,0,1806,1808, + 1,0,0,0,1807,1798,1,0,0,0,1807,1799,1,0,0,0,1807,1800,1,0,0,0,1807, + 1802,1,0,0,0,1807,1803,1,0,0,0,1808,91,1,0,0,0,1809,1811,3,236,118, + 0,1810,1812,7,14,0,0,1811,1810,1,0,0,0,1811,1812,1,0,0,0,1812,1815, + 1,0,0,0,1813,1814,5,174,0,0,1814,1816,7,15,0,0,1815,1813,1,0,0,0, + 1815,1816,1,0,0,0,1816,93,1,0,0,0,1817,1819,3,130,65,0,1818,1820, + 3,96,48,0,1819,1818,1,0,0,0,1820,1821,1,0,0,0,1821,1819,1,0,0,0, + 1821,1822,1,0,0,0,1822,95,1,0,0,0,1823,1825,3,100,50,0,1824,1826, + 3,122,61,0,1825,1824,1,0,0,0,1825,1826,1,0,0,0,1826,1827,1,0,0,0, + 1827,1828,3,84,42,0,1828,1851,1,0,0,0,1829,1833,3,102,51,0,1830, + 1832,3,172,86,0,1831,1830,1,0,0,0,1832,1835,1,0,0,0,1833,1831,1, + 0,0,0,1833,1834,1,0,0,0,1834,1837,1,0,0,0,1835,1833,1,0,0,0,1836, + 1838,3,122,61,0,1837,1836,1,0,0,0,1837,1838,1,0,0,0,1838,1840,1, + 0,0,0,1839,1841,3,134,67,0,1840,1839,1,0,0,0,1840,1841,1,0,0,0,1841, + 1843,1,0,0,0,1842,1844,3,124,62,0,1843,1842,1,0,0,0,1843,1844,1, + 0,0,0,1844,1846,1,0,0,0,1845,1847,3,306,153,0,1846,1845,1,0,0,0, + 1846,1847,1,0,0,0,1847,1848,1,0,0,0,1848,1849,3,84,42,0,1849,1851, + 1,0,0,0,1850,1823,1,0,0,0,1850,1829,1,0,0,0,1851,97,1,0,0,0,1852, + 1854,3,100,50,0,1853,1855,3,130,65,0,1854,1853,1,0,0,0,1854,1855, + 1,0,0,0,1855,1859,1,0,0,0,1856,1858,3,172,86,0,1857,1856,1,0,0,0, + 1858,1861,1,0,0,0,1859,1857,1,0,0,0,1859,1860,1,0,0,0,1860,1863, + 1,0,0,0,1861,1859,1,0,0,0,1862,1864,3,122,61,0,1863,1862,1,0,0,0, + 1863,1864,1,0,0,0,1864,1866,1,0,0,0,1865,1867,3,134,67,0,1866,1865, + 1,0,0,0,1866,1867,1,0,0,0,1867,1869,1,0,0,0,1868,1870,3,124,62,0, + 1869,1868,1,0,0,0,1869,1870,1,0,0,0,1870,1872,1,0,0,0,1871,1873, + 3,306,153,0,1872,1871,1,0,0,0,1872,1873,1,0,0,0,1873,1897,1,0,0, + 0,1874,1876,3,102,51,0,1875,1877,3,130,65,0,1876,1875,1,0,0,0,1876, + 1877,1,0,0,0,1877,1881,1,0,0,0,1878,1880,3,172,86,0,1879,1878,1, + 0,0,0,1880,1883,1,0,0,0,1881,1879,1,0,0,0,1881,1882,1,0,0,0,1882, + 1885,1,0,0,0,1883,1881,1,0,0,0,1884,1886,3,122,61,0,1885,1884,1, + 0,0,0,1885,1886,1,0,0,0,1886,1888,1,0,0,0,1887,1889,3,134,67,0,1888, + 1887,1,0,0,0,1888,1889,1,0,0,0,1889,1891,1,0,0,0,1890,1892,3,124, + 62,0,1891,1890,1,0,0,0,1891,1892,1,0,0,0,1892,1894,1,0,0,0,1893, + 1895,3,306,153,0,1894,1893,1,0,0,0,1894,1895,1,0,0,0,1895,1897,1, + 0,0,0,1896,1852,1,0,0,0,1896,1874,1,0,0,0,1897,99,1,0,0,0,1898,1899, + 5,233,0,0,1899,1900,5,275,0,0,1900,1902,5,2,0,0,1901,1903,3,174, + 87,0,1902,1901,1,0,0,0,1902,1903,1,0,0,0,1903,1904,1,0,0,0,1904, + 1905,3,238,119,0,1905,1906,5,3,0,0,1906,1918,1,0,0,0,1907,1909,5, + 154,0,0,1908,1910,3,174,87,0,1909,1908,1,0,0,0,1909,1910,1,0,0,0, + 1910,1911,1,0,0,0,1911,1918,3,238,119,0,1912,1914,5,210,0,0,1913, + 1915,3,174,87,0,1914,1913,1,0,0,0,1914,1915,1,0,0,0,1915,1916,1, + 0,0,0,1916,1918,3,238,119,0,1917,1898,1,0,0,0,1917,1907,1,0,0,0, + 1917,1912,1,0,0,0,1918,1920,1,0,0,0,1919,1921,3,210,105,0,1920,1919, + 1,0,0,0,1920,1921,1,0,0,0,1921,1924,1,0,0,0,1922,1923,5,208,0,0, + 1923,1925,3,338,169,0,1924,1922,1,0,0,0,1924,1925,1,0,0,0,1925,1926, + 1,0,0,0,1926,1927,5,293,0,0,1927,1940,3,338,169,0,1928,1938,5,20, + 0,0,1929,1939,3,192,96,0,1930,1939,3,288,144,0,1931,1934,5,2,0,0, + 1932,1935,3,192,96,0,1933,1935,3,288,144,0,1934,1932,1,0,0,0,1934, + 1933,1,0,0,0,1935,1936,1,0,0,0,1936,1937,5,3,0,0,1937,1939,1,0,0, + 0,1938,1929,1,0,0,0,1938,1930,1,0,0,0,1938,1931,1,0,0,0,1939,1941, + 1,0,0,0,1940,1928,1,0,0,0,1940,1941,1,0,0,0,1941,1943,1,0,0,0,1942, + 1944,3,210,105,0,1943,1942,1,0,0,0,1943,1944,1,0,0,0,1944,1947,1, + 0,0,0,1945,1946,5,207,0,0,1946,1948,3,338,169,0,1947,1945,1,0,0, + 0,1947,1948,1,0,0,0,1948,101,1,0,0,0,1949,1953,5,233,0,0,1950,1952, + 3,126,63,0,1951,1950,1,0,0,0,1952,1955,1,0,0,0,1953,1951,1,0,0,0, + 1953,1954,1,0,0,0,1954,1957,1,0,0,0,1955,1953,1,0,0,0,1956,1958, + 3,174,87,0,1957,1956,1,0,0,0,1957,1958,1,0,0,0,1958,1959,1,0,0,0, + 1959,1960,3,226,113,0,1960,103,1,0,0,0,1961,1962,5,239,0,0,1962, + 1963,3,118,59,0,1963,105,1,0,0,0,1964,1965,5,300,0,0,1965,1968,5, + 155,0,0,1966,1967,5,14,0,0,1967,1969,3,240,120,0,1968,1966,1,0,0, + 0,1968,1969,1,0,0,0,1969,1970,1,0,0,0,1970,1971,5,265,0,0,1971,1972, + 3,112,56,0,1972,107,1,0,0,0,1973,1974,5,300,0,0,1974,1975,5,172, + 0,0,1975,1978,5,155,0,0,1976,1977,5,28,0,0,1977,1979,5,261,0,0,1978, + 1976,1,0,0,0,1978,1979,1,0,0,0,1979,1982,1,0,0,0,1980,1981,5,14, + 0,0,1981,1983,3,240,120,0,1982,1980,1,0,0,0,1982,1983,1,0,0,0,1983, + 1984,1,0,0,0,1984,1985,5,265,0,0,1985,1986,3,114,57,0,1986,109,1, + 0,0,0,1987,1988,5,300,0,0,1988,1989,5,172,0,0,1989,1990,5,155,0, + 0,1990,1991,5,28,0,0,1991,1994,5,247,0,0,1992,1993,5,14,0,0,1993, + 1995,3,240,120,0,1994,1992,1,0,0,0,1994,1995,1,0,0,0,1995,1996,1, + 0,0,0,1996,1997,5,265,0,0,1997,1998,3,116,58,0,1998,111,1,0,0,0, + 1999,2007,5,72,0,0,2000,2001,5,290,0,0,2001,2002,5,239,0,0,2002, + 2007,5,318,0,0,2003,2004,5,290,0,0,2004,2005,5,239,0,0,2005,2007, + 3,118,59,0,2006,1999,1,0,0,0,2006,2000,1,0,0,0,2006,2003,1,0,0,0, + 2007,113,1,0,0,0,2008,2009,5,129,0,0,2009,2027,5,318,0,0,2010,2011, + 5,129,0,0,2011,2012,5,2,0,0,2012,2013,3,212,106,0,2013,2014,5,3, + 0,0,2014,2015,5,294,0,0,2015,2016,5,2,0,0,2016,2021,3,236,118,0, + 2017,2018,5,4,0,0,2018,2020,3,236,118,0,2019,2017,1,0,0,0,2020,2023, + 1,0,0,0,2021,2019,1,0,0,0,2021,2022,1,0,0,0,2022,2024,1,0,0,0,2023, + 2021,1,0,0,0,2024,2025,5,3,0,0,2025,2027,1,0,0,0,2026,2008,1,0,0, + 0,2026,2010,1,0,0,0,2027,115,1,0,0,0,2028,2033,5,72,0,0,2029,2030, + 5,290,0,0,2030,2031,5,239,0,0,2031,2033,3,118,59,0,2032,2028,1,0, + 0,0,2032,2029,1,0,0,0,2033,117,1,0,0,0,2034,2039,3,120,60,0,2035, + 2036,5,4,0,0,2036,2038,3,120,60,0,2037,2035,1,0,0,0,2038,2041,1, + 0,0,0,2039,2037,1,0,0,0,2039,2040,1,0,0,0,2040,119,1,0,0,0,2041, + 2039,1,0,0,0,2042,2043,3,214,107,0,2043,2044,5,308,0,0,2044,2045, + 3,236,118,0,2045,121,1,0,0,0,2046,2047,5,301,0,0,2047,2048,3,240, + 120,0,2048,123,1,0,0,0,2049,2050,5,116,0,0,2050,2051,3,240,120,0, + 2051,125,1,0,0,0,2052,2053,5,328,0,0,2053,2060,3,128,64,0,2054,2056, + 5,4,0,0,2055,2054,1,0,0,0,2055,2056,1,0,0,0,2056,2057,1,0,0,0,2057, + 2059,3,128,64,0,2058,2055,1,0,0,0,2059,2062,1,0,0,0,2060,2058,1, + 0,0,0,2060,2061,1,0,0,0,2061,2063,1,0,0,0,2062,2060,1,0,0,0,2063, + 2064,5,329,0,0,2064,127,1,0,0,0,2065,2079,3,326,163,0,2066,2067, + 3,326,163,0,2067,2068,5,2,0,0,2068,2073,3,248,124,0,2069,2070,5, + 4,0,0,2070,2072,3,248,124,0,2071,2069,1,0,0,0,2072,2075,1,0,0,0, + 2073,2071,1,0,0,0,2073,2074,1,0,0,0,2074,2076,1,0,0,0,2075,2073, + 1,0,0,0,2076,2077,5,3,0,0,2077,2079,1,0,0,0,2078,2065,1,0,0,0,2078, + 2066,1,0,0,0,2079,129,1,0,0,0,2080,2081,5,107,0,0,2081,2086,3,176, + 88,0,2082,2083,5,4,0,0,2083,2085,3,176,88,0,2084,2082,1,0,0,0,2085, + 2088,1,0,0,0,2086,2084,1,0,0,0,2086,2087,1,0,0,0,2087,2092,1,0,0, + 0,2088,2086,1,0,0,0,2089,2091,3,172,86,0,2090,2089,1,0,0,0,2091, + 2094,1,0,0,0,2092,2090,1,0,0,0,2092,2093,1,0,0,0,2093,2096,1,0,0, + 0,2094,2092,1,0,0,0,2095,2097,3,144,72,0,2096,2095,1,0,0,0,2096, + 2097,1,0,0,0,2097,2099,1,0,0,0,2098,2100,3,150,75,0,2099,2098,1, + 0,0,0,2099,2100,1,0,0,0,2100,131,1,0,0,0,2101,2103,5,103,0,0,2102, + 2101,1,0,0,0,2102,2103,1,0,0,0,2103,2104,1,0,0,0,2104,2105,7,16, + 0,0,2105,2106,5,20,0,0,2106,2107,5,175,0,0,2107,2116,3,342,171,0, + 2108,2110,5,103,0,0,2109,2108,1,0,0,0,2109,2110,1,0,0,0,2110,2111, + 1,0,0,0,2111,2112,7,17,0,0,2112,2113,5,20,0,0,2113,2114,5,175,0, + 0,2114,2116,3,244,122,0,2115,2102,1,0,0,0,2115,2109,1,0,0,0,2116, + 133,1,0,0,0,2117,2118,5,114,0,0,2118,2119,5,28,0,0,2119,2124,3,136, + 68,0,2120,2121,5,4,0,0,2121,2123,3,136,68,0,2122,2120,1,0,0,0,2123, + 2126,1,0,0,0,2124,2122,1,0,0,0,2124,2125,1,0,0,0,2125,2157,1,0,0, + 0,2126,2124,1,0,0,0,2127,2128,5,114,0,0,2128,2129,5,28,0,0,2129, + 2134,3,236,118,0,2130,2131,5,4,0,0,2131,2133,3,236,118,0,2132,2130, + 1,0,0,0,2133,2136,1,0,0,0,2134,2132,1,0,0,0,2134,2135,1,0,0,0,2135, + 2154,1,0,0,0,2136,2134,1,0,0,0,2137,2138,5,303,0,0,2138,2155,5,226, + 0,0,2139,2140,5,303,0,0,2140,2155,5,55,0,0,2141,2142,5,115,0,0,2142, + 2143,5,241,0,0,2143,2144,5,2,0,0,2144,2149,3,142,71,0,2145,2146, + 5,4,0,0,2146,2148,3,142,71,0,2147,2145,1,0,0,0,2148,2151,1,0,0,0, + 2149,2147,1,0,0,0,2149,2150,1,0,0,0,2150,2152,1,0,0,0,2151,2149, + 1,0,0,0,2152,2153,5,3,0,0,2153,2155,1,0,0,0,2154,2137,1,0,0,0,2154, + 2139,1,0,0,0,2154,2141,1,0,0,0,2154,2155,1,0,0,0,2155,2157,1,0,0, + 0,2156,2117,1,0,0,0,2156,2127,1,0,0,0,2157,135,1,0,0,0,2158,2161, + 3,138,69,0,2159,2161,3,236,118,0,2160,2158,1,0,0,0,2160,2159,1,0, + 0,0,2161,137,1,0,0,0,2162,2163,7,18,0,0,2163,2164,5,2,0,0,2164,2169, + 3,142,71,0,2165,2166,5,4,0,0,2166,2168,3,142,71,0,2167,2165,1,0, + 0,0,2168,2171,1,0,0,0,2169,2167,1,0,0,0,2169,2170,1,0,0,0,2170,2172, + 1,0,0,0,2171,2169,1,0,0,0,2172,2173,5,3,0,0,2173,2188,1,0,0,0,2174, + 2175,5,115,0,0,2175,2176,5,241,0,0,2176,2177,5,2,0,0,2177,2182,3, + 140,70,0,2178,2179,5,4,0,0,2179,2181,3,140,70,0,2180,2178,1,0,0, + 0,2181,2184,1,0,0,0,2182,2180,1,0,0,0,2182,2183,1,0,0,0,2183,2185, + 1,0,0,0,2184,2182,1,0,0,0,2185,2186,5,3,0,0,2186,2188,1,0,0,0,2187, + 2162,1,0,0,0,2187,2174,1,0,0,0,2188,139,1,0,0,0,2189,2192,3,138, + 69,0,2190,2192,3,142,71,0,2191,2189,1,0,0,0,2191,2190,1,0,0,0,2192, + 141,1,0,0,0,2193,2202,5,2,0,0,2194,2199,3,236,118,0,2195,2196,5, + 4,0,0,2196,2198,3,236,118,0,2197,2195,1,0,0,0,2198,2201,1,0,0,0, + 2199,2197,1,0,0,0,2199,2200,1,0,0,0,2200,2203,1,0,0,0,2201,2199, + 1,0,0,0,2202,2194,1,0,0,0,2202,2203,1,0,0,0,2203,2204,1,0,0,0,2204, + 2207,5,3,0,0,2205,2207,3,236,118,0,2206,2193,1,0,0,0,2206,2205,1, + 0,0,0,2207,143,1,0,0,0,2208,2209,5,196,0,0,2209,2210,5,2,0,0,2210, + 2211,3,226,113,0,2211,2212,5,103,0,0,2212,2213,3,146,73,0,2213,2214, + 5,122,0,0,2214,2215,5,2,0,0,2215,2220,3,148,74,0,2216,2217,5,4,0, + 0,2217,2219,3,148,74,0,2218,2216,1,0,0,0,2219,2222,1,0,0,0,2220, + 2218,1,0,0,0,2220,2221,1,0,0,0,2221,2223,1,0,0,0,2222,2220,1,0,0, + 0,2223,2224,5,3,0,0,2224,2225,5,3,0,0,2225,145,1,0,0,0,2226,2239, + 3,326,163,0,2227,2228,5,2,0,0,2228,2233,3,326,163,0,2229,2230,5, + 4,0,0,2230,2232,3,326,163,0,2231,2229,1,0,0,0,2232,2235,1,0,0,0, + 2233,2231,1,0,0,0,2233,2234,1,0,0,0,2234,2236,1,0,0,0,2235,2233, + 1,0,0,0,2236,2237,5,3,0,0,2237,2239,1,0,0,0,2238,2226,1,0,0,0,2238, + 2227,1,0,0,0,2239,147,1,0,0,0,2240,2245,3,236,118,0,2241,2243,5, + 20,0,0,2242,2241,1,0,0,0,2242,2243,1,0,0,0,2243,2244,1,0,0,0,2244, + 2246,3,326,163,0,2245,2242,1,0,0,0,2245,2246,1,0,0,0,2246,149,1, + 0,0,0,2247,2249,5,288,0,0,2248,2250,3,152,76,0,2249,2248,1,0,0,0, + 2249,2250,1,0,0,0,2250,2251,1,0,0,0,2251,2252,5,2,0,0,2252,2253, + 3,154,77,0,2253,2258,5,3,0,0,2254,2256,5,20,0,0,2255,2254,1,0,0, + 0,2255,2256,1,0,0,0,2256,2257,1,0,0,0,2257,2259,3,326,163,0,2258, + 2255,1,0,0,0,2258,2259,1,0,0,0,2259,151,1,0,0,0,2260,2261,7,19,0, + 0,2261,2262,5,174,0,0,2262,153,1,0,0,0,2263,2266,3,156,78,0,2264, + 2266,3,158,79,0,2265,2263,1,0,0,0,2265,2264,1,0,0,0,2266,155,1,0, + 0,0,2267,2268,3,162,81,0,2268,2269,5,103,0,0,2269,2270,3,164,82, + 0,2270,2271,5,122,0,0,2271,2272,5,2,0,0,2272,2277,3,166,83,0,2273, + 2274,5,4,0,0,2274,2276,3,166,83,0,2275,2273,1,0,0,0,2276,2279,1, + 0,0,0,2277,2275,1,0,0,0,2277,2278,1,0,0,0,2278,2280,1,0,0,0,2279, + 2277,1,0,0,0,2280,2281,5,3,0,0,2281,157,1,0,0,0,2282,2283,5,2,0, + 0,2283,2288,3,162,81,0,2284,2285,5,4,0,0,2285,2287,3,162,81,0,2286, + 2284,1,0,0,0,2287,2290,1,0,0,0,2288,2286,1,0,0,0,2288,2289,1,0,0, + 0,2289,2291,1,0,0,0,2290,2288,1,0,0,0,2291,2292,5,3,0,0,2292,2293, + 5,103,0,0,2293,2294,3,164,82,0,2294,2295,5,122,0,0,2295,2296,5,2, + 0,0,2296,2301,3,160,80,0,2297,2298,5,4,0,0,2298,2300,3,160,80,0, + 2299,2297,1,0,0,0,2300,2303,1,0,0,0,2301,2299,1,0,0,0,2301,2302, + 1,0,0,0,2302,2304,1,0,0,0,2303,2301,1,0,0,0,2304,2305,5,3,0,0,2305, + 159,1,0,0,0,2306,2307,5,2,0,0,2307,2312,3,168,84,0,2308,2309,5,4, + 0,0,2309,2311,3,168,84,0,2310,2308,1,0,0,0,2311,2314,1,0,0,0,2312, + 2310,1,0,0,0,2312,2313,1,0,0,0,2313,2315,1,0,0,0,2314,2312,1,0,0, + 0,2315,2317,5,3,0,0,2316,2318,3,170,85,0,2317,2316,1,0,0,0,2317, + 2318,1,0,0,0,2318,161,1,0,0,0,2319,2320,3,326,163,0,2320,163,1,0, + 0,0,2321,2322,3,326,163,0,2322,165,1,0,0,0,2323,2325,3,168,84,0, + 2324,2326,3,170,85,0,2325,2324,1,0,0,0,2325,2326,1,0,0,0,2326,167, + 1,0,0,0,2327,2328,3,214,107,0,2328,169,1,0,0,0,2329,2331,5,20,0, + 0,2330,2329,1,0,0,0,2330,2331,1,0,0,0,2331,2332,1,0,0,0,2332,2333, + 3,326,163,0,2333,171,1,0,0,0,2334,2335,5,138,0,0,2335,2337,5,296, + 0,0,2336,2338,5,184,0,0,2337,2336,1,0,0,0,2337,2338,1,0,0,0,2338, + 2339,1,0,0,0,2339,2340,3,320,160,0,2340,2349,5,2,0,0,2341,2346,3, + 236,118,0,2342,2343,5,4,0,0,2343,2345,3,236,118,0,2344,2342,1,0, + 0,0,2345,2348,1,0,0,0,2346,2344,1,0,0,0,2346,2347,1,0,0,0,2347,2350, + 1,0,0,0,2348,2346,1,0,0,0,2349,2341,1,0,0,0,2349,2350,1,0,0,0,2350, + 2351,1,0,0,0,2351,2352,5,3,0,0,2352,2364,3,326,163,0,2353,2355,5, + 20,0,0,2354,2353,1,0,0,0,2354,2355,1,0,0,0,2355,2356,1,0,0,0,2356, + 2361,3,326,163,0,2357,2358,5,4,0,0,2358,2360,3,326,163,0,2359,2357, + 1,0,0,0,2360,2363,1,0,0,0,2361,2359,1,0,0,0,2361,2362,1,0,0,0,2362, + 2365,1,0,0,0,2363,2361,1,0,0,0,2364,2354,1,0,0,0,2364,2365,1,0,0, + 0,2365,173,1,0,0,0,2366,2367,7,20,0,0,2367,175,1,0,0,0,2368,2370, + 5,138,0,0,2369,2368,1,0,0,0,2369,2370,1,0,0,0,2370,2371,1,0,0,0, + 2371,2375,3,202,101,0,2372,2374,3,178,89,0,2373,2372,1,0,0,0,2374, + 2377,1,0,0,0,2375,2373,1,0,0,0,2375,2376,1,0,0,0,2376,177,1,0,0, + 0,2377,2375,1,0,0,0,2378,2382,3,180,90,0,2379,2382,3,144,72,0,2380, + 2382,3,150,75,0,2381,2378,1,0,0,0,2381,2379,1,0,0,0,2381,2380,1, + 0,0,0,2382,179,1,0,0,0,2383,2384,3,182,91,0,2384,2386,5,135,0,0, + 2385,2387,5,138,0,0,2386,2385,1,0,0,0,2386,2387,1,0,0,0,2387,2388, + 1,0,0,0,2388,2390,3,202,101,0,2389,2391,3,184,92,0,2390,2389,1,0, + 0,0,2390,2391,1,0,0,0,2391,2401,1,0,0,0,2392,2393,5,170,0,0,2393, + 2394,3,182,91,0,2394,2396,5,135,0,0,2395,2397,5,138,0,0,2396,2395, + 1,0,0,0,2396,2397,1,0,0,0,2397,2398,1,0,0,0,2398,2399,3,202,101, + 0,2399,2401,1,0,0,0,2400,2383,1,0,0,0,2400,2392,1,0,0,0,2401,181, + 1,0,0,0,2402,2404,5,126,0,0,2403,2402,1,0,0,0,2403,2404,1,0,0,0, + 2404,2427,1,0,0,0,2405,2427,5,54,0,0,2406,2408,5,141,0,0,2407,2409, + 5,184,0,0,2408,2407,1,0,0,0,2408,2409,1,0,0,0,2409,2427,1,0,0,0, + 2410,2412,5,141,0,0,2411,2410,1,0,0,0,2411,2412,1,0,0,0,2412,2413, + 1,0,0,0,2413,2427,5,234,0,0,2414,2416,5,221,0,0,2415,2417,5,184, + 0,0,2416,2415,1,0,0,0,2416,2417,1,0,0,0,2417,2427,1,0,0,0,2418,2420, + 5,108,0,0,2419,2421,5,184,0,0,2420,2419,1,0,0,0,2420,2421,1,0,0, + 0,2421,2427,1,0,0,0,2422,2424,5,141,0,0,2423,2422,1,0,0,0,2423,2424, + 1,0,0,0,2424,2425,1,0,0,0,2425,2427,5,15,0,0,2426,2403,1,0,0,0,2426, + 2405,1,0,0,0,2426,2406,1,0,0,0,2426,2411,1,0,0,0,2426,2414,1,0,0, + 0,2426,2418,1,0,0,0,2426,2423,1,0,0,0,2427,183,1,0,0,0,2428,2429, + 5,177,0,0,2429,2433,3,240,120,0,2430,2431,5,293,0,0,2431,2433,3, + 190,95,0,2432,2428,1,0,0,0,2432,2430,1,0,0,0,2433,185,1,0,0,0,2434, + 2435,5,260,0,0,2435,2437,5,2,0,0,2436,2438,3,188,94,0,2437,2436, + 1,0,0,0,2437,2438,1,0,0,0,2438,2439,1,0,0,0,2439,2444,5,3,0,0,2440, + 2441,5,215,0,0,2441,2442,5,2,0,0,2442,2443,5,335,0,0,2443,2445,5, + 3,0,0,2444,2440,1,0,0,0,2444,2445,1,0,0,0,2445,187,1,0,0,0,2446, + 2448,5,317,0,0,2447,2446,1,0,0,0,2447,2448,1,0,0,0,2448,2449,1,0, + 0,0,2449,2450,7,21,0,0,2450,2471,5,195,0,0,2451,2452,3,236,118,0, + 2452,2453,5,228,0,0,2453,2471,1,0,0,0,2454,2455,5,26,0,0,2455,2456, + 5,335,0,0,2456,2457,5,183,0,0,2457,2458,5,175,0,0,2458,2467,5,335, + 0,0,2459,2465,5,177,0,0,2460,2466,3,326,163,0,2461,2462,3,320,160, + 0,2462,2463,5,2,0,0,2463,2464,5,3,0,0,2464,2466,1,0,0,0,2465,2460, + 1,0,0,0,2465,2461,1,0,0,0,2466,2468,1,0,0,0,2467,2459,1,0,0,0,2467, + 2468,1,0,0,0,2468,2471,1,0,0,0,2469,2471,3,236,118,0,2470,2447,1, + 0,0,0,2470,2451,1,0,0,0,2470,2454,1,0,0,0,2470,2469,1,0,0,0,2471, + 189,1,0,0,0,2472,2473,5,2,0,0,2473,2474,3,192,96,0,2474,2475,5,3, + 0,0,2475,191,1,0,0,0,2476,2481,3,322,161,0,2477,2478,5,4,0,0,2478, + 2480,3,322,161,0,2479,2477,1,0,0,0,2480,2483,1,0,0,0,2481,2479,1, + 0,0,0,2481,2482,1,0,0,0,2482,193,1,0,0,0,2483,2481,1,0,0,0,2484, + 2485,5,2,0,0,2485,2490,3,196,98,0,2486,2487,5,4,0,0,2487,2489,3, + 196,98,0,2488,2486,1,0,0,0,2489,2492,1,0,0,0,2490,2488,1,0,0,0,2490, + 2491,1,0,0,0,2491,2493,1,0,0,0,2492,2490,1,0,0,0,2493,2494,5,3,0, + 0,2494,195,1,0,0,0,2495,2497,3,322,161,0,2496,2498,7,14,0,0,2497, + 2496,1,0,0,0,2497,2498,1,0,0,0,2498,197,1,0,0,0,2499,2500,5,2,0, + 0,2500,2505,3,200,100,0,2501,2502,5,4,0,0,2502,2504,3,200,100,0, + 2503,2501,1,0,0,0,2504,2507,1,0,0,0,2505,2503,1,0,0,0,2505,2506, + 1,0,0,0,2506,2508,1,0,0,0,2507,2505,1,0,0,0,2508,2509,5,3,0,0,2509, + 199,1,0,0,0,2510,2512,3,326,163,0,2511,2513,3,34,17,0,2512,2511, + 1,0,0,0,2512,2513,1,0,0,0,2513,201,1,0,0,0,2514,2516,3,214,107,0, + 2515,2517,3,132,66,0,2516,2515,1,0,0,0,2516,2517,1,0,0,0,2517,2519, + 1,0,0,0,2518,2520,3,186,93,0,2519,2518,1,0,0,0,2519,2520,1,0,0,0, + 2520,2521,1,0,0,0,2521,2522,3,208,104,0,2522,2542,1,0,0,0,2523,2524, + 5,2,0,0,2524,2525,3,36,18,0,2525,2527,5,3,0,0,2526,2528,3,186,93, + 0,2527,2526,1,0,0,0,2527,2528,1,0,0,0,2528,2529,1,0,0,0,2529,2530, + 3,208,104,0,2530,2542,1,0,0,0,2531,2532,5,2,0,0,2532,2533,3,176, + 88,0,2533,2535,5,3,0,0,2534,2536,3,186,93,0,2535,2534,1,0,0,0,2535, + 2536,1,0,0,0,2536,2537,1,0,0,0,2537,2538,3,208,104,0,2538,2542,1, + 0,0,0,2539,2542,3,204,102,0,2540,2542,3,206,103,0,2541,2514,1,0, + 0,0,2541,2523,1,0,0,0,2541,2531,1,0,0,0,2541,2539,1,0,0,0,2541,2540, + 1,0,0,0,2542,203,1,0,0,0,2543,2544,5,294,0,0,2544,2549,3,236,118, + 0,2545,2546,5,4,0,0,2546,2548,3,236,118,0,2547,2545,1,0,0,0,2548, + 2551,1,0,0,0,2549,2547,1,0,0,0,2549,2550,1,0,0,0,2550,2552,1,0,0, + 0,2551,2549,1,0,0,0,2552,2553,3,208,104,0,2553,205,1,0,0,0,2554, + 2555,3,318,159,0,2555,2564,5,2,0,0,2556,2561,3,236,118,0,2557,2558, + 5,4,0,0,2558,2560,3,236,118,0,2559,2557,1,0,0,0,2560,2563,1,0,0, + 0,2561,2559,1,0,0,0,2561,2562,1,0,0,0,2562,2565,1,0,0,0,2563,2561, + 1,0,0,0,2564,2556,1,0,0,0,2564,2565,1,0,0,0,2565,2566,1,0,0,0,2566, + 2567,5,3,0,0,2567,2568,3,208,104,0,2568,207,1,0,0,0,2569,2571,5, + 20,0,0,2570,2569,1,0,0,0,2570,2571,1,0,0,0,2571,2572,1,0,0,0,2572, + 2574,3,328,164,0,2573,2575,3,190,95,0,2574,2573,1,0,0,0,2574,2575, + 1,0,0,0,2575,2577,1,0,0,0,2576,2570,1,0,0,0,2576,2577,1,0,0,0,2577, + 209,1,0,0,0,2578,2579,5,227,0,0,2579,2580,5,105,0,0,2580,2581,5, + 236,0,0,2581,2585,3,338,169,0,2582,2583,5,303,0,0,2583,2584,5,237, + 0,0,2584,2586,3,62,31,0,2585,2582,1,0,0,0,2585,2586,1,0,0,0,2586, + 2628,1,0,0,0,2587,2588,5,227,0,0,2588,2589,5,105,0,0,2589,2599,5, + 73,0,0,2590,2591,5,98,0,0,2591,2592,5,264,0,0,2592,2593,5,28,0,0, + 2593,2597,3,338,169,0,2594,2595,5,86,0,0,2595,2596,5,28,0,0,2596, + 2598,3,338,169,0,2597,2594,1,0,0,0,2597,2598,1,0,0,0,2598,2600,1, + 0,0,0,2599,2590,1,0,0,0,2599,2600,1,0,0,0,2600,2606,1,0,0,0,2601, + 2602,5,42,0,0,2602,2603,5,134,0,0,2603,2604,5,264,0,0,2604,2605, + 5,28,0,0,2605,2607,3,338,169,0,2606,2601,1,0,0,0,2606,2607,1,0,0, + 0,2607,2613,1,0,0,0,2608,2609,5,154,0,0,2609,2610,5,136,0,0,2610, + 2611,5,264,0,0,2611,2612,5,28,0,0,2612,2614,3,338,169,0,2613,2608, + 1,0,0,0,2613,2614,1,0,0,0,2614,2619,1,0,0,0,2615,2616,5,145,0,0, + 2616,2617,5,264,0,0,2617,2618,5,28,0,0,2618,2620,3,338,169,0,2619, + 2615,1,0,0,0,2619,2620,1,0,0,0,2620,2625,1,0,0,0,2621,2622,5,173, + 0,0,2622,2623,5,71,0,0,2623,2624,5,20,0,0,2624,2626,3,338,169,0, + 2625,2621,1,0,0,0,2625,2626,1,0,0,0,2626,2628,1,0,0,0,2627,2578, + 1,0,0,0,2627,2587,1,0,0,0,2628,211,1,0,0,0,2629,2634,3,214,107,0, + 2630,2631,5,4,0,0,2631,2633,3,214,107,0,2632,2630,1,0,0,0,2633,2636, + 1,0,0,0,2634,2632,1,0,0,0,2634,2635,1,0,0,0,2635,213,1,0,0,0,2636, + 2634,1,0,0,0,2637,2642,3,322,161,0,2638,2639,5,5,0,0,2639,2641,3, + 322,161,0,2640,2638,1,0,0,0,2641,2644,1,0,0,0,2642,2640,1,0,0,0, + 2642,2643,1,0,0,0,2643,215,1,0,0,0,2644,2642,1,0,0,0,2645,2650,3, + 218,109,0,2646,2647,5,4,0,0,2647,2649,3,218,109,0,2648,2646,1,0, + 0,0,2649,2652,1,0,0,0,2650,2648,1,0,0,0,2650,2651,1,0,0,0,2651,217, + 1,0,0,0,2652,2650,1,0,0,0,2653,2656,3,214,107,0,2654,2655,5,180, + 0,0,2655,2657,3,62,31,0,2656,2654,1,0,0,0,2656,2657,1,0,0,0,2657, + 219,1,0,0,0,2658,2659,3,322,161,0,2659,2660,5,5,0,0,2660,2662,1, + 0,0,0,2661,2658,1,0,0,0,2661,2662,1,0,0,0,2662,2663,1,0,0,0,2663, + 2664,3,322,161,0,2664,221,1,0,0,0,2665,2666,3,322,161,0,2666,2667, + 5,5,0,0,2667,2669,1,0,0,0,2668,2665,1,0,0,0,2668,2669,1,0,0,0,2669, + 2670,1,0,0,0,2670,2671,3,322,161,0,2671,223,1,0,0,0,2672,2680,3, + 236,118,0,2673,2675,5,20,0,0,2674,2673,1,0,0,0,2674,2675,1,0,0,0, + 2675,2678,1,0,0,0,2676,2679,3,322,161,0,2677,2679,3,190,95,0,2678, + 2676,1,0,0,0,2678,2677,1,0,0,0,2679,2681,1,0,0,0,2680,2674,1,0,0, + 0,2680,2681,1,0,0,0,2681,225,1,0,0,0,2682,2687,3,224,112,0,2683, + 2684,5,4,0,0,2684,2686,3,224,112,0,2685,2683,1,0,0,0,2686,2689,1, + 0,0,0,2687,2685,1,0,0,0,2687,2688,1,0,0,0,2688,227,1,0,0,0,2689, + 2687,1,0,0,0,2690,2691,5,2,0,0,2691,2696,3,230,115,0,2692,2693,5, + 4,0,0,2693,2695,3,230,115,0,2694,2692,1,0,0,0,2695,2698,1,0,0,0, + 2696,2694,1,0,0,0,2696,2697,1,0,0,0,2697,2699,1,0,0,0,2698,2696, + 1,0,0,0,2699,2700,5,3,0,0,2700,229,1,0,0,0,2701,2704,3,232,116,0, + 2702,2704,3,290,145,0,2703,2701,1,0,0,0,2703,2702,1,0,0,0,2704,231, + 1,0,0,0,2705,2719,3,320,160,0,2706,2707,3,326,163,0,2707,2708,5, + 2,0,0,2708,2713,3,234,117,0,2709,2710,5,4,0,0,2710,2712,3,234,117, + 0,2711,2709,1,0,0,0,2712,2715,1,0,0,0,2713,2711,1,0,0,0,2713,2714, + 1,0,0,0,2714,2716,1,0,0,0,2715,2713,1,0,0,0,2716,2717,5,3,0,0,2717, + 2719,1,0,0,0,2718,2705,1,0,0,0,2718,2706,1,0,0,0,2719,233,1,0,0, + 0,2720,2723,3,320,160,0,2721,2723,3,250,125,0,2722,2720,1,0,0,0, + 2722,2721,1,0,0,0,2723,235,1,0,0,0,2724,2725,3,240,120,0,2725,237, + 1,0,0,0,2726,2731,3,236,118,0,2727,2728,5,4,0,0,2728,2730,3,236, + 118,0,2729,2727,1,0,0,0,2730,2733,1,0,0,0,2731,2729,1,0,0,0,2731, + 2732,1,0,0,0,2732,239,1,0,0,0,2733,2731,1,0,0,0,2734,2735,6,120, + -1,0,2735,2736,5,172,0,0,2736,2747,3,240,120,5,2737,2738,5,90,0, + 0,2738,2739,5,2,0,0,2739,2740,3,36,18,0,2740,2741,5,3,0,0,2741,2747, + 1,0,0,0,2742,2744,3,244,122,0,2743,2745,3,242,121,0,2744,2743,1, + 0,0,0,2744,2745,1,0,0,0,2745,2747,1,0,0,0,2746,2734,1,0,0,0,2746, + 2737,1,0,0,0,2746,2742,1,0,0,0,2747,2756,1,0,0,0,2748,2749,10,2, + 0,0,2749,2750,5,14,0,0,2750,2755,3,240,120,3,2751,2752,10,1,0,0, + 2752,2753,5,181,0,0,2753,2755,3,240,120,2,2754,2748,1,0,0,0,2754, + 2751,1,0,0,0,2755,2758,1,0,0,0,2756,2754,1,0,0,0,2756,2757,1,0,0, + 0,2757,241,1,0,0,0,2758,2756,1,0,0,0,2759,2761,5,172,0,0,2760,2759, + 1,0,0,0,2760,2761,1,0,0,0,2761,2762,1,0,0,0,2762,2763,5,24,0,0,2763, + 2764,3,244,122,0,2764,2765,5,14,0,0,2765,2766,3,244,122,0,2766,2842, + 1,0,0,0,2767,2769,5,172,0,0,2768,2767,1,0,0,0,2768,2769,1,0,0,0, + 2769,2770,1,0,0,0,2770,2771,5,122,0,0,2771,2772,5,2,0,0,2772,2777, + 3,236,118,0,2773,2774,5,4,0,0,2774,2776,3,236,118,0,2775,2773,1, + 0,0,0,2776,2779,1,0,0,0,2777,2775,1,0,0,0,2777,2778,1,0,0,0,2778, + 2780,1,0,0,0,2779,2777,1,0,0,0,2780,2781,5,3,0,0,2781,2842,1,0,0, + 0,2782,2784,5,172,0,0,2783,2782,1,0,0,0,2783,2784,1,0,0,0,2784,2785, + 1,0,0,0,2785,2786,5,122,0,0,2786,2787,5,2,0,0,2787,2788,3,36,18, + 0,2788,2789,5,3,0,0,2789,2842,1,0,0,0,2790,2792,5,172,0,0,2791,2790, + 1,0,0,0,2791,2792,1,0,0,0,2792,2793,1,0,0,0,2793,2794,5,222,0,0, + 2794,2842,3,244,122,0,2795,2797,5,172,0,0,2796,2795,1,0,0,0,2796, + 2797,1,0,0,0,2797,2798,1,0,0,0,2798,2799,7,22,0,0,2799,2813,7,23, + 0,0,2800,2801,5,2,0,0,2801,2814,5,3,0,0,2802,2803,5,2,0,0,2803,2808, + 3,236,118,0,2804,2805,5,4,0,0,2805,2807,3,236,118,0,2806,2804,1, + 0,0,0,2807,2810,1,0,0,0,2808,2806,1,0,0,0,2808,2809,1,0,0,0,2809, + 2811,1,0,0,0,2810,2808,1,0,0,0,2811,2812,5,3,0,0,2812,2814,1,0,0, + 0,2813,2800,1,0,0,0,2813,2802,1,0,0,0,2814,2842,1,0,0,0,2815,2817, + 5,172,0,0,2816,2815,1,0,0,0,2816,2817,1,0,0,0,2817,2818,1,0,0,0, + 2818,2819,7,22,0,0,2819,2822,3,244,122,0,2820,2821,5,85,0,0,2821, + 2823,3,338,169,0,2822,2820,1,0,0,0,2822,2823,1,0,0,0,2823,2842,1, + 0,0,0,2824,2826,5,133,0,0,2825,2827,5,172,0,0,2826,2825,1,0,0,0, + 2826,2827,1,0,0,0,2827,2828,1,0,0,0,2828,2842,5,173,0,0,2829,2831, + 5,133,0,0,2830,2832,5,172,0,0,2831,2830,1,0,0,0,2831,2832,1,0,0, + 0,2832,2833,1,0,0,0,2833,2842,7,24,0,0,2834,2836,5,133,0,0,2835, + 2837,5,172,0,0,2836,2835,1,0,0,0,2836,2837,1,0,0,0,2837,2838,1,0, + 0,0,2838,2839,5,79,0,0,2839,2840,5,107,0,0,2840,2842,3,244,122,0, + 2841,2760,1,0,0,0,2841,2768,1,0,0,0,2841,2783,1,0,0,0,2841,2791, + 1,0,0,0,2841,2796,1,0,0,0,2841,2816,1,0,0,0,2841,2824,1,0,0,0,2841, + 2829,1,0,0,0,2841,2834,1,0,0,0,2842,243,1,0,0,0,2843,2844,6,122, + -1,0,2844,2848,3,248,124,0,2845,2846,7,25,0,0,2846,2848,3,244,122, + 7,2847,2843,1,0,0,0,2847,2845,1,0,0,0,2848,2870,1,0,0,0,2849,2850, + 10,6,0,0,2850,2851,7,26,0,0,2851,2869,3,244,122,7,2852,2853,10,5, + 0,0,2853,2854,7,27,0,0,2854,2869,3,244,122,6,2855,2856,10,4,0,0, + 2856,2857,5,322,0,0,2857,2869,3,244,122,5,2858,2859,10,3,0,0,2859, + 2860,5,325,0,0,2860,2869,3,244,122,4,2861,2862,10,2,0,0,2862,2863, + 5,323,0,0,2863,2869,3,244,122,3,2864,2865,10,1,0,0,2865,2866,3,252, + 126,0,2866,2867,3,244,122,2,2867,2869,1,0,0,0,2868,2849,1,0,0,0, + 2868,2852,1,0,0,0,2868,2855,1,0,0,0,2868,2858,1,0,0,0,2868,2861, + 1,0,0,0,2868,2864,1,0,0,0,2869,2872,1,0,0,0,2870,2868,1,0,0,0,2870, + 2871,1,0,0,0,2871,245,1,0,0,0,2872,2870,1,0,0,0,2873,2874,7,28,0, + 0,2874,247,1,0,0,0,2875,2876,6,124,-1,0,2876,3114,7,29,0,0,2877, + 2878,7,30,0,0,2878,2879,5,2,0,0,2879,2880,3,246,123,0,2880,2881, + 5,4,0,0,2881,2882,3,244,122,0,2882,2883,5,4,0,0,2883,2884,3,244, + 122,0,2884,2885,5,3,0,0,2885,3114,1,0,0,0,2886,2887,7,31,0,0,2887, + 2888,5,2,0,0,2888,2889,3,246,123,0,2889,2890,5,4,0,0,2890,2891,3, + 244,122,0,2891,2892,5,4,0,0,2892,2893,3,244,122,0,2893,2894,5,3, + 0,0,2894,3114,1,0,0,0,2895,2897,5,31,0,0,2896,2898,3,304,152,0,2897, + 2896,1,0,0,0,2898,2899,1,0,0,0,2899,2897,1,0,0,0,2899,2900,1,0,0, + 0,2900,2903,1,0,0,0,2901,2902,5,83,0,0,2902,2904,3,236,118,0,2903, + 2901,1,0,0,0,2903,2904,1,0,0,0,2904,2905,1,0,0,0,2905,2906,5,84, + 0,0,2906,3114,1,0,0,0,2907,2908,5,31,0,0,2908,2910,3,236,118,0,2909, + 2911,3,304,152,0,2910,2909,1,0,0,0,2911,2912,1,0,0,0,2912,2910,1, + 0,0,0,2912,2913,1,0,0,0,2913,2916,1,0,0,0,2914,2915,5,83,0,0,2915, + 2917,3,236,118,0,2916,2914,1,0,0,0,2916,2917,1,0,0,0,2917,2918,1, + 0,0,0,2918,2919,5,84,0,0,2919,3114,1,0,0,0,2920,2921,7,32,0,0,2921, + 2922,5,2,0,0,2922,2923,3,236,118,0,2923,2924,5,20,0,0,2924,2925, + 3,278,139,0,2925,2926,5,3,0,0,2926,3114,1,0,0,0,2927,2928,5,252, + 0,0,2928,2937,5,2,0,0,2929,2934,3,224,112,0,2930,2931,5,4,0,0,2931, + 2933,3,224,112,0,2932,2930,1,0,0,0,2933,2936,1,0,0,0,2934,2932,1, + 0,0,0,2934,2935,1,0,0,0,2935,2938,1,0,0,0,2936,2934,1,0,0,0,2937, + 2929,1,0,0,0,2937,2938,1,0,0,0,2938,2939,1,0,0,0,2939,3114,5,3,0, + 0,2940,2941,5,101,0,0,2941,2942,5,2,0,0,2942,2945,3,236,118,0,2943, + 2944,5,120,0,0,2944,2946,5,174,0,0,2945,2943,1,0,0,0,2945,2946,1, + 0,0,0,2946,2947,1,0,0,0,2947,2948,5,3,0,0,2948,3114,1,0,0,0,2949, + 2950,5,17,0,0,2950,2951,5,2,0,0,2951,2954,3,236,118,0,2952,2953, + 5,120,0,0,2953,2955,5,174,0,0,2954,2952,1,0,0,0,2954,2955,1,0,0, + 0,2955,2956,1,0,0,0,2956,2957,5,3,0,0,2957,3114,1,0,0,0,2958,2959, + 5,137,0,0,2959,2960,5,2,0,0,2960,2963,3,236,118,0,2961,2962,5,120, + 0,0,2962,2964,5,174,0,0,2963,2961,1,0,0,0,2963,2964,1,0,0,0,2964, + 2965,1,0,0,0,2965,2966,5,3,0,0,2966,3114,1,0,0,0,2967,2968,5,198, + 0,0,2968,2969,5,2,0,0,2969,2970,3,244,122,0,2970,2971,5,122,0,0, + 2971,2972,3,244,122,0,2972,2973,5,3,0,0,2973,3114,1,0,0,0,2974,3114, + 3,250,125,0,2975,3114,5,318,0,0,2976,2977,3,320,160,0,2977,2978, + 5,5,0,0,2978,2979,5,318,0,0,2979,3114,1,0,0,0,2980,2981,5,2,0,0, + 2981,2984,3,224,112,0,2982,2983,5,4,0,0,2983,2985,3,224,112,0,2984, + 2982,1,0,0,0,2985,2986,1,0,0,0,2986,2984,1,0,0,0,2986,2987,1,0,0, + 0,2987,2988,1,0,0,0,2988,2989,5,3,0,0,2989,3114,1,0,0,0,2990,2991, + 5,2,0,0,2991,2992,3,36,18,0,2992,2993,5,3,0,0,2993,3114,1,0,0,0, + 2994,2995,3,318,159,0,2995,3007,5,2,0,0,2996,2998,3,174,87,0,2997, + 2996,1,0,0,0,2997,2998,1,0,0,0,2998,2999,1,0,0,0,2999,3004,3,236, + 118,0,3000,3001,5,4,0,0,3001,3003,3,236,118,0,3002,3000,1,0,0,0, + 3003,3006,1,0,0,0,3004,3002,1,0,0,0,3004,3005,1,0,0,0,3005,3008, + 1,0,0,0,3006,3004,1,0,0,0,3007,2997,1,0,0,0,3007,3008,1,0,0,0,3008, + 3009,1,0,0,0,3009,3016,5,3,0,0,3010,3011,5,99,0,0,3011,3012,5,2, + 0,0,3012,3013,5,301,0,0,3013,3014,3,240,120,0,3014,3015,5,3,0,0, + 3015,3017,1,0,0,0,3016,3010,1,0,0,0,3016,3017,1,0,0,0,3017,3020, + 1,0,0,0,3018,3019,7,33,0,0,3019,3021,5,174,0,0,3020,3018,1,0,0,0, + 3020,3021,1,0,0,0,3021,3024,1,0,0,0,3022,3023,5,186,0,0,3023,3025, + 3,310,155,0,3024,3022,1,0,0,0,3024,3025,1,0,0,0,3025,3114,1,0,0, + 0,3026,3027,3,326,163,0,3027,3028,5,327,0,0,3028,3029,3,236,118, + 0,3029,3114,1,0,0,0,3030,3031,5,2,0,0,3031,3034,3,326,163,0,3032, + 3033,5,4,0,0,3033,3035,3,326,163,0,3034,3032,1,0,0,0,3035,3036,1, + 0,0,0,3036,3034,1,0,0,0,3036,3037,1,0,0,0,3037,3038,1,0,0,0,3038, + 3039,5,3,0,0,3039,3040,5,327,0,0,3040,3041,3,236,118,0,3041,3114, + 1,0,0,0,3042,3114,3,326,163,0,3043,3044,5,2,0,0,3044,3045,3,236, + 118,0,3045,3046,5,3,0,0,3046,3114,1,0,0,0,3047,3048,5,95,0,0,3048, + 3049,5,2,0,0,3049,3050,3,326,163,0,3050,3051,5,107,0,0,3051,3052, + 3,244,122,0,3052,3053,5,3,0,0,3053,3114,1,0,0,0,3054,3055,7,34,0, + 0,3055,3056,5,2,0,0,3056,3057,3,244,122,0,3057,3058,7,35,0,0,3058, + 3061,3,244,122,0,3059,3060,7,36,0,0,3060,3062,3,244,122,0,3061,3059, + 1,0,0,0,3061,3062,1,0,0,0,3062,3063,1,0,0,0,3063,3064,5,3,0,0,3064, + 3114,1,0,0,0,3065,3066,5,276,0,0,3066,3068,5,2,0,0,3067,3069,7,37, + 0,0,3068,3067,1,0,0,0,3068,3069,1,0,0,0,3069,3071,1,0,0,0,3070,3072, + 3,244,122,0,3071,3070,1,0,0,0,3071,3072,1,0,0,0,3072,3073,1,0,0, + 0,3073,3074,5,107,0,0,3074,3075,3,244,122,0,3075,3076,5,3,0,0,3076, + 3114,1,0,0,0,3077,3078,5,188,0,0,3078,3079,5,2,0,0,3079,3080,3,244, + 122,0,3080,3081,5,197,0,0,3081,3082,3,244,122,0,3082,3083,5,107, + 0,0,3083,3086,3,244,122,0,3084,3085,5,103,0,0,3085,3087,3,244,122, + 0,3086,3084,1,0,0,0,3086,3087,1,0,0,0,3087,3088,1,0,0,0,3088,3089, + 5,3,0,0,3089,3114,1,0,0,0,3090,3091,7,38,0,0,3091,3092,5,2,0,0,3092, + 3093,3,244,122,0,3093,3094,5,3,0,0,3094,3095,5,304,0,0,3095,3096, + 5,114,0,0,3096,3097,5,2,0,0,3097,3098,5,182,0,0,3098,3099,5,28,0, + 0,3099,3100,3,92,46,0,3100,3107,5,3,0,0,3101,3102,5,99,0,0,3102, + 3103,5,2,0,0,3103,3104,5,301,0,0,3104,3105,3,240,120,0,3105,3106, + 5,3,0,0,3106,3108,1,0,0,0,3107,3101,1,0,0,0,3107,3108,1,0,0,0,3108, + 3111,1,0,0,0,3109,3110,5,186,0,0,3110,3112,3,310,155,0,3111,3109, + 1,0,0,0,3111,3112,1,0,0,0,3112,3114,1,0,0,0,3113,2875,1,0,0,0,3113, + 2877,1,0,0,0,3113,2886,1,0,0,0,3113,2895,1,0,0,0,3113,2907,1,0,0, + 0,3113,2920,1,0,0,0,3113,2927,1,0,0,0,3113,2940,1,0,0,0,3113,2949, + 1,0,0,0,3113,2958,1,0,0,0,3113,2967,1,0,0,0,3113,2974,1,0,0,0,3113, + 2975,1,0,0,0,3113,2976,1,0,0,0,3113,2980,1,0,0,0,3113,2990,1,0,0, + 0,3113,2994,1,0,0,0,3113,3026,1,0,0,0,3113,3030,1,0,0,0,3113,3042, + 1,0,0,0,3113,3043,1,0,0,0,3113,3047,1,0,0,0,3113,3054,1,0,0,0,3113, + 3065,1,0,0,0,3113,3077,1,0,0,0,3113,3090,1,0,0,0,3114,3125,1,0,0, + 0,3115,3116,10,9,0,0,3116,3117,5,6,0,0,3117,3118,3,244,122,0,3118, + 3119,5,7,0,0,3119,3124,1,0,0,0,3120,3121,10,7,0,0,3121,3122,5,5, + 0,0,3122,3124,3,326,163,0,3123,3115,1,0,0,0,3123,3120,1,0,0,0,3124, + 3127,1,0,0,0,3125,3123,1,0,0,0,3125,3126,1,0,0,0,3126,249,1,0,0, + 0,3127,3125,1,0,0,0,3128,3143,5,173,0,0,3129,3130,5,326,0,0,3130, + 3143,3,326,163,0,3131,3143,3,260,130,0,3132,3133,3,326,163,0,3133, + 3134,3,338,169,0,3134,3143,1,0,0,0,3135,3143,3,334,167,0,3136,3143, + 3,258,129,0,3137,3139,3,338,169,0,3138,3137,1,0,0,0,3139,3140,1, + 0,0,0,3140,3138,1,0,0,0,3140,3141,1,0,0,0,3141,3143,1,0,0,0,3142, + 3128,1,0,0,0,3142,3129,1,0,0,0,3142,3131,1,0,0,0,3142,3132,1,0,0, + 0,3142,3135,1,0,0,0,3142,3136,1,0,0,0,3142,3138,1,0,0,0,3143,251, + 1,0,0,0,3144,3145,7,39,0,0,3145,253,1,0,0,0,3146,3147,7,40,0,0,3147, + 255,1,0,0,0,3148,3149,7,41,0,0,3149,257,1,0,0,0,3150,3151,7,42,0, + 0,3151,259,1,0,0,0,3152,3155,5,131,0,0,3153,3156,3,262,131,0,3154, + 3156,3,266,133,0,3155,3153,1,0,0,0,3155,3154,1,0,0,0,3156,261,1, + 0,0,0,3157,3159,3,264,132,0,3158,3160,3,268,134,0,3159,3158,1,0, + 0,0,3159,3160,1,0,0,0,3160,263,1,0,0,0,3161,3162,3,270,135,0,3162, + 3163,3,272,136,0,3163,3165,1,0,0,0,3164,3161,1,0,0,0,3165,3166,1, + 0,0,0,3166,3164,1,0,0,0,3166,3167,1,0,0,0,3167,265,1,0,0,0,3168, + 3171,3,268,134,0,3169,3172,3,264,132,0,3170,3172,3,268,134,0,3171, + 3169,1,0,0,0,3171,3170,1,0,0,0,3171,3172,1,0,0,0,3172,267,1,0,0, + 0,3173,3174,3,270,135,0,3174,3175,3,274,137,0,3175,3176,5,270,0, + 0,3176,3177,3,274,137,0,3177,269,1,0,0,0,3178,3180,7,43,0,0,3179, + 3178,1,0,0,0,3179,3180,1,0,0,0,3180,3184,1,0,0,0,3181,3185,5,335, + 0,0,3182,3185,5,337,0,0,3183,3185,3,338,169,0,3184,3181,1,0,0,0, + 3184,3182,1,0,0,0,3184,3183,1,0,0,0,3185,271,1,0,0,0,3186,3187,7, + 44,0,0,3187,273,1,0,0,0,3188,3189,7,45,0,0,3189,275,1,0,0,0,3190, + 3194,5,101,0,0,3191,3192,5,9,0,0,3192,3194,3,322,161,0,3193,3190, + 1,0,0,0,3193,3191,1,0,0,0,3194,277,1,0,0,0,3195,3196,5,19,0,0,3196, + 3197,5,312,0,0,3197,3198,3,278,139,0,3198,3199,5,314,0,0,3199,3242, + 1,0,0,0,3200,3201,5,154,0,0,3201,3202,5,312,0,0,3202,3203,3,278, + 139,0,3203,3204,5,4,0,0,3204,3205,3,278,139,0,3205,3206,5,314,0, + 0,3206,3242,1,0,0,0,3207,3214,5,252,0,0,3208,3210,5,312,0,0,3209, + 3211,3,300,150,0,3210,3209,1,0,0,0,3210,3211,1,0,0,0,3211,3212,1, + 0,0,0,3212,3215,5,314,0,0,3213,3215,5,310,0,0,3214,3208,1,0,0,0, + 3214,3213,1,0,0,0,3215,3242,1,0,0,0,3216,3217,5,131,0,0,3217,3220, + 7,46,0,0,3218,3219,5,270,0,0,3219,3221,5,163,0,0,3220,3218,1,0,0, + 0,3220,3221,1,0,0,0,3221,3242,1,0,0,0,3222,3223,5,131,0,0,3223,3226, + 7,47,0,0,3224,3225,5,270,0,0,3225,3227,7,48,0,0,3226,3224,1,0,0, + 0,3226,3227,1,0,0,0,3227,3242,1,0,0,0,3228,3239,3,326,163,0,3229, + 3230,5,2,0,0,3230,3235,5,335,0,0,3231,3232,5,4,0,0,3232,3234,5,335, + 0,0,3233,3231,1,0,0,0,3234,3237,1,0,0,0,3235,3233,1,0,0,0,3235,3236, + 1,0,0,0,3236,3238,1,0,0,0,3237,3235,1,0,0,0,3238,3240,5,3,0,0,3239, + 3229,1,0,0,0,3239,3240,1,0,0,0,3240,3242,1,0,0,0,3241,3195,1,0,0, + 0,3241,3200,1,0,0,0,3241,3207,1,0,0,0,3241,3216,1,0,0,0,3241,3222, + 1,0,0,0,3241,3228,1,0,0,0,3242,279,1,0,0,0,3243,3248,3,282,141,0, + 3244,3245,5,4,0,0,3245,3247,3,282,141,0,3246,3244,1,0,0,0,3247,3250, + 1,0,0,0,3248,3246,1,0,0,0,3248,3249,1,0,0,0,3249,281,1,0,0,0,3250, + 3248,1,0,0,0,3251,3252,3,214,107,0,3252,3256,3,278,139,0,3253,3255, + 3,284,142,0,3254,3253,1,0,0,0,3255,3258,1,0,0,0,3256,3254,1,0,0, + 0,3256,3257,1,0,0,0,3257,283,1,0,0,0,3258,3256,1,0,0,0,3259,3260, + 5,172,0,0,3260,3265,5,173,0,0,3261,3265,3,286,143,0,3262,3265,3, + 34,17,0,3263,3265,3,276,138,0,3264,3259,1,0,0,0,3264,3261,1,0,0, + 0,3264,3262,1,0,0,0,3264,3263,1,0,0,0,3265,285,1,0,0,0,3266,3267, + 5,70,0,0,3267,3268,3,236,118,0,3268,287,1,0,0,0,3269,3274,3,290, + 145,0,3270,3271,5,4,0,0,3271,3273,3,290,145,0,3272,3270,1,0,0,0, + 3273,3276,1,0,0,0,3274,3272,1,0,0,0,3274,3275,1,0,0,0,3275,289,1, + 0,0,0,3276,3274,1,0,0,0,3277,3278,3,322,161,0,3278,3281,3,278,139, + 0,3279,3280,5,172,0,0,3280,3282,5,173,0,0,3281,3279,1,0,0,0,3281, + 3282,1,0,0,0,3282,3284,1,0,0,0,3283,3285,3,34,17,0,3284,3283,1,0, + 0,0,3284,3285,1,0,0,0,3285,291,1,0,0,0,3286,3291,3,294,147,0,3287, + 3288,5,4,0,0,3288,3290,3,294,147,0,3289,3287,1,0,0,0,3290,3293,1, + 0,0,0,3291,3289,1,0,0,0,3291,3292,1,0,0,0,3292,293,1,0,0,0,3293, + 3291,1,0,0,0,3294,3295,3,322,161,0,3295,3299,3,278,139,0,3296,3298, + 3,296,148,0,3297,3296,1,0,0,0,3298,3301,1,0,0,0,3299,3297,1,0,0, + 0,3299,3300,1,0,0,0,3300,295,1,0,0,0,3301,3299,1,0,0,0,3302,3303, + 5,172,0,0,3303,3308,5,173,0,0,3304,3308,3,286,143,0,3305,3308,3, + 298,149,0,3306,3308,3,34,17,0,3307,3302,1,0,0,0,3307,3304,1,0,0, + 0,3307,3305,1,0,0,0,3307,3306,1,0,0,0,3308,297,1,0,0,0,3309,3310, + 5,111,0,0,3310,3311,5,12,0,0,3311,3312,5,20,0,0,3312,3313,5,2,0, + 0,3313,3314,3,236,118,0,3314,3315,5,3,0,0,3315,299,1,0,0,0,3316, + 3321,3,302,151,0,3317,3318,5,4,0,0,3318,3320,3,302,151,0,3319,3317, + 1,0,0,0,3320,3323,1,0,0,0,3321,3319,1,0,0,0,3321,3322,1,0,0,0,3322, + 301,1,0,0,0,3323,3321,1,0,0,0,3324,3326,3,326,163,0,3325,3327,5, + 326,0,0,3326,3325,1,0,0,0,3326,3327,1,0,0,0,3327,3328,1,0,0,0,3328, + 3331,3,278,139,0,3329,3330,5,172,0,0,3330,3332,5,173,0,0,3331,3329, + 1,0,0,0,3331,3332,1,0,0,0,3332,3334,1,0,0,0,3333,3335,3,34,17,0, + 3334,3333,1,0,0,0,3334,3335,1,0,0,0,3335,303,1,0,0,0,3336,3337,5, + 300,0,0,3337,3338,3,236,118,0,3338,3339,5,265,0,0,3339,3340,3,236, + 118,0,3340,305,1,0,0,0,3341,3342,5,302,0,0,3342,3347,3,308,154,0, + 3343,3344,5,4,0,0,3344,3346,3,308,154,0,3345,3343,1,0,0,0,3346,3349, + 1,0,0,0,3347,3345,1,0,0,0,3347,3348,1,0,0,0,3348,307,1,0,0,0,3349, + 3347,1,0,0,0,3350,3351,3,322,161,0,3351,3352,5,20,0,0,3352,3353, + 3,310,155,0,3353,309,1,0,0,0,3354,3401,3,322,161,0,3355,3356,5,2, + 0,0,3356,3357,3,322,161,0,3357,3358,5,3,0,0,3358,3401,1,0,0,0,3359, + 3394,5,2,0,0,3360,3361,5,38,0,0,3361,3362,5,28,0,0,3362,3367,3,236, + 118,0,3363,3364,5,4,0,0,3364,3366,3,236,118,0,3365,3363,1,0,0,0, + 3366,3369,1,0,0,0,3367,3365,1,0,0,0,3367,3368,1,0,0,0,3368,3395, + 1,0,0,0,3369,3367,1,0,0,0,3370,3371,7,49,0,0,3371,3372,5,28,0,0, + 3372,3377,3,236,118,0,3373,3374,5,4,0,0,3374,3376,3,236,118,0,3375, + 3373,1,0,0,0,3376,3379,1,0,0,0,3377,3375,1,0,0,0,3377,3378,1,0,0, + 0,3378,3381,1,0,0,0,3379,3377,1,0,0,0,3380,3370,1,0,0,0,3380,3381, + 1,0,0,0,3381,3392,1,0,0,0,3382,3383,7,50,0,0,3383,3384,5,28,0,0, + 3384,3389,3,92,46,0,3385,3386,5,4,0,0,3386,3388,3,92,46,0,3387,3385, + 1,0,0,0,3388,3391,1,0,0,0,3389,3387,1,0,0,0,3389,3390,1,0,0,0,3390, + 3393,1,0,0,0,3391,3389,1,0,0,0,3392,3382,1,0,0,0,3392,3393,1,0,0, + 0,3393,3395,1,0,0,0,3394,3360,1,0,0,0,3394,3380,1,0,0,0,3395,3397, + 1,0,0,0,3396,3398,3,312,156,0,3397,3396,1,0,0,0,3397,3398,1,0,0, + 0,3398,3399,1,0,0,0,3399,3401,5,3,0,0,3400,3354,1,0,0,0,3400,3355, + 1,0,0,0,3400,3359,1,0,0,0,3401,311,1,0,0,0,3402,3403,5,206,0,0,3403, + 3419,3,314,157,0,3404,3405,5,228,0,0,3405,3419,3,314,157,0,3406, + 3407,5,206,0,0,3407,3408,5,24,0,0,3408,3409,3,314,157,0,3409,3410, + 5,14,0,0,3410,3411,3,314,157,0,3411,3419,1,0,0,0,3412,3413,5,228, + 0,0,3413,3414,5,24,0,0,3414,3415,3,314,157,0,3415,3416,5,14,0,0, + 3416,3417,3,314,157,0,3417,3419,1,0,0,0,3418,3402,1,0,0,0,3418,3404, + 1,0,0,0,3418,3406,1,0,0,0,3418,3412,1,0,0,0,3419,313,1,0,0,0,3420, + 3421,5,282,0,0,3421,3428,7,51,0,0,3422,3423,5,56,0,0,3423,3428,5, + 227,0,0,3424,3425,3,236,118,0,3425,3426,7,51,0,0,3426,3428,1,0,0, + 0,3427,3420,1,0,0,0,3427,3422,1,0,0,0,3427,3424,1,0,0,0,3428,315, + 1,0,0,0,3429,3434,3,320,160,0,3430,3431,5,4,0,0,3431,3433,3,320, + 160,0,3432,3430,1,0,0,0,3433,3436,1,0,0,0,3434,3432,1,0,0,0,3434, + 3435,1,0,0,0,3435,317,1,0,0,0,3436,3434,1,0,0,0,3437,3442,3,320, + 160,0,3438,3442,5,99,0,0,3439,3442,5,141,0,0,3440,3442,5,221,0,0, + 3441,3437,1,0,0,0,3441,3438,1,0,0,0,3441,3439,1,0,0,0,3441,3440, + 1,0,0,0,3442,319,1,0,0,0,3443,3448,3,326,163,0,3444,3445,5,5,0,0, + 3445,3447,3,326,163,0,3446,3444,1,0,0,0,3447,3450,1,0,0,0,3448,3446, + 1,0,0,0,3448,3449,1,0,0,0,3449,321,1,0,0,0,3450,3448,1,0,0,0,3451, + 3452,3,326,163,0,3452,3453,3,324,162,0,3453,323,1,0,0,0,3454,3455, + 5,317,0,0,3455,3457,3,326,163,0,3456,3454,1,0,0,0,3457,3458,1,0, + 0,0,3458,3456,1,0,0,0,3458,3459,1,0,0,0,3459,3462,1,0,0,0,3460,3462, + 1,0,0,0,3461,3456,1,0,0,0,3461,3460,1,0,0,0,3462,325,1,0,0,0,3463, + 3466,3,328,164,0,3464,3466,3,346,173,0,3465,3463,1,0,0,0,3465,3464, + 1,0,0,0,3466,327,1,0,0,0,3467,3472,5,341,0,0,3468,3472,3,330,165, + 0,3469,3472,3,344,172,0,3470,3472,3,348,174,0,3471,3467,1,0,0,0, + 3471,3468,1,0,0,0,3471,3469,1,0,0,0,3471,3470,1,0,0,0,3472,329,1, + 0,0,0,3473,3474,7,52,0,0,3474,331,1,0,0,0,3475,3476,5,342,0,0,3476, + 333,1,0,0,0,3477,3479,5,317,0,0,3478,3477,1,0,0,0,3478,3479,1,0, + 0,0,3479,3480,1,0,0,0,3480,3518,5,336,0,0,3481,3483,5,317,0,0,3482, + 3481,1,0,0,0,3482,3483,1,0,0,0,3483,3484,1,0,0,0,3484,3518,5,337, + 0,0,3485,3487,5,317,0,0,3486,3485,1,0,0,0,3486,3487,1,0,0,0,3487, + 3488,1,0,0,0,3488,3518,7,53,0,0,3489,3491,5,317,0,0,3490,3489,1, + 0,0,0,3490,3491,1,0,0,0,3491,3492,1,0,0,0,3492,3518,5,335,0,0,3493, + 3495,5,317,0,0,3494,3493,1,0,0,0,3494,3495,1,0,0,0,3495,3496,1,0, + 0,0,3496,3518,5,332,0,0,3497,3499,5,317,0,0,3498,3497,1,0,0,0,3498, + 3499,1,0,0,0,3499,3500,1,0,0,0,3500,3518,5,333,0,0,3501,3503,5,317, + 0,0,3502,3501,1,0,0,0,3502,3503,1,0,0,0,3503,3504,1,0,0,0,3504,3518, + 5,334,0,0,3505,3507,5,317,0,0,3506,3505,1,0,0,0,3506,3507,1,0,0, + 0,3507,3508,1,0,0,0,3508,3518,5,339,0,0,3509,3511,5,317,0,0,3510, + 3509,1,0,0,0,3510,3511,1,0,0,0,3511,3512,1,0,0,0,3512,3518,5,338, + 0,0,3513,3515,5,317,0,0,3514,3513,1,0,0,0,3514,3515,1,0,0,0,3515, + 3516,1,0,0,0,3516,3518,5,340,0,0,3517,3478,1,0,0,0,3517,3482,1,0, + 0,0,3517,3486,1,0,0,0,3517,3490,1,0,0,0,3517,3494,1,0,0,0,3517,3498, + 1,0,0,0,3517,3502,1,0,0,0,3517,3506,1,0,0,0,3517,3510,1,0,0,0,3517, + 3514,1,0,0,0,3518,335,1,0,0,0,3519,3520,5,280,0,0,3520,3531,3,278, + 139,0,3521,3531,3,34,17,0,3522,3531,3,276,138,0,3523,3524,7,54,0, + 0,3524,3525,5,172,0,0,3525,3531,5,173,0,0,3526,3527,5,239,0,0,3527, + 3531,3,286,143,0,3528,3529,5,82,0,0,3529,3531,5,70,0,0,3530,3519, + 1,0,0,0,3530,3521,1,0,0,0,3530,3522,1,0,0,0,3530,3523,1,0,0,0,3530, + 3526,1,0,0,0,3530,3528,1,0,0,0,3531,337,1,0,0,0,3532,3533,7,55,0, + 0,3533,339,1,0,0,0,3534,3537,3,338,169,0,3535,3537,5,173,0,0,3536, + 3534,1,0,0,0,3536,3535,1,0,0,0,3537,341,1,0,0,0,3538,3541,5,335, + 0,0,3539,3541,3,338,169,0,3540,3538,1,0,0,0,3540,3539,1,0,0,0,3541, + 343,1,0,0,0,3542,3543,7,56,0,0,3543,345,1,0,0,0,3544,3545,7,57,0, + 0,3545,347,1,0,0,0,3546,3547,7,58,0,0,3547,349,1,0,0,0,460,354,379, + 392,399,407,409,429,433,439,442,445,452,455,459,462,469,480,482, + 490,493,497,500,506,517,523,528,562,575,600,609,613,619,623,628, + 634,646,654,660,673,678,694,701,705,711,726,730,736,742,745,748, + 754,758,766,768,777,780,789,794,800,807,810,816,827,830,834,839, + 844,851,854,857,864,869,878,886,892,895,898,904,908,913,916,920, + 922,930,938,941,946,952,958,961,965,968,972,1000,1003,1007,1013, + 1016,1019,1025,1033,1038,1044,1050,1053,1060,1067,1075,1092,1106, + 1109,1115,1124,1133,1141,1146,1151,1158,1164,1169,1177,1180,1184, + 1196,1200,1207,1323,1331,1339,1348,1358,1362,1365,1371,1377,1389, + 1401,1406,1415,1423,1430,1432,1435,1440,1444,1449,1452,1457,1466, + 1471,1474,1479,1483,1488,1490,1494,1503,1511,1517,1528,1535,1544, + 1549,1552,1574,1576,1585,1592,1595,1602,1606,1612,1620,1631,1642, + 1650,1656,1668,1675,1682,1694,1702,1708,1714,1717,1726,1729,1738, + 1741,1750,1753,1762,1765,1768,1773,1775,1779,1790,1795,1807,1811, + 1815,1821,1825,1833,1837,1840,1843,1846,1850,1854,1859,1863,1866, + 1869,1872,1876,1881,1885,1888,1891,1894,1896,1902,1909,1914,1917, + 1920,1924,1934,1938,1940,1943,1947,1953,1957,1968,1978,1982,1994, + 2006,2021,2026,2032,2039,2055,2060,2073,2078,2086,2092,2096,2099, + 2102,2109,2115,2124,2134,2149,2154,2156,2160,2169,2182,2187,2191, + 2199,2202,2206,2220,2233,2238,2242,2245,2249,2255,2258,2265,2277, + 2288,2301,2312,2317,2325,2330,2337,2346,2349,2354,2361,2364,2369, + 2375,2381,2386,2390,2396,2400,2403,2408,2411,2416,2420,2423,2426, + 2432,2437,2444,2447,2465,2467,2470,2481,2490,2497,2505,2512,2516, + 2519,2527,2535,2541,2549,2561,2564,2570,2574,2576,2585,2597,2599, + 2606,2613,2619,2625,2627,2634,2642,2650,2656,2661,2668,2674,2678, + 2680,2687,2696,2703,2713,2718,2722,2731,2744,2746,2754,2756,2760, + 2768,2777,2783,2791,2796,2808,2813,2816,2822,2826,2831,2836,2841, + 2847,2868,2870,2899,2903,2912,2916,2934,2937,2945,2954,2963,2986, + 2997,3004,3007,3016,3020,3024,3036,3061,3068,3071,3086,3107,3111, + 3113,3123,3125,3140,3142,3155,3159,3166,3171,3179,3184,3193,3210, + 3214,3220,3226,3235,3239,3241,3248,3256,3264,3274,3281,3284,3291, + 3299,3307,3321,3326,3331,3334,3347,3367,3377,3380,3389,3392,3394, + 3397,3400,3418,3427,3434,3441,3448,3458,3461,3465,3471,3478,3482, + 3486,3490,3494,3498,3502,3506,3510,3514,3517,3530,3536,3540 + ] + +class SqlBaseParser ( Parser ): + + grammarFileName = "SqlBaseParser.g4" + + atn = ATNDeserializer().deserialize(serializedATN()) + + decisionsToDFA = [ DFA(ds, i) for i, ds in enumerate(atn.decisionToState) ] + + sharedContextCache = PredictionContextCache() + + literalNames = [ "", "';'", "'('", "')'", "','", "'.'", "'['", + "']'", "'ADD'", "'AFTER'", "'ALL'", "'ALTER'", "'ALWAYS'", + "'ANALYZE'", "'AND'", "'ANTI'", "'ANY'", "'ANY_VALUE'", + "'ARCHIVE'", "'ARRAY'", "'AS'", "'ASC'", "'AT'", "'AUTHORIZATION'", + "'BETWEEN'", "'BOTH'", "'BUCKET'", "'BUCKETS'", "'BY'", + "'CACHE'", "'CASCADE'", "'CASE'", "'CAST'", "'CATALOG'", + "'CATALOGS'", "'CHANGE'", "'CHECK'", "'CLEAR'", "'CLUSTER'", + "'CLUSTERED'", "'CODEGEN'", "'COLLATE'", "'COLLECTION'", + "'COLUMN'", "'COLUMNS'", "'COMMENT'", "'COMMIT'", "'COMPACT'", + "'COMPACTIONS'", "'COMPUTE'", "'CONCATENATE'", "'CONSTRAINT'", + "'COST'", "'CREATE'", "'CROSS'", "'CUBE'", "'CURRENT'", + "'CURRENT_DATE'", "'CURRENT_TIME'", "'CURRENT_TIMESTAMP'", + "'CURRENT_USER'", "'DAY'", "'DAYS'", "'DAYOFYEAR'", + "'DATA'", "'DATABASE'", "'DATABASES'", "'DATEADD'", + "'DATEDIFF'", "'DBPROPERTIES'", "'DEFAULT'", "'DEFINED'", + "'DELETE'", "'DELIMITED'", "'DESC'", "'DESCRIBE'", + "'DFS'", "'DIRECTORIES'", "'DIRECTORY'", "'DISTINCT'", + "'DISTRIBUTE'", "'DIV'", "'DROP'", "'ELSE'", "'END'", + "'ESCAPE'", "'ESCAPED'", "'EXCEPT'", "'EXCHANGE'", + "'EXCLUDE'", "'EXISTS'", "'EXPLAIN'", "'EXPORT'", "'EXTENDED'", + "'EXTERNAL'", "'EXTRACT'", "'FALSE'", "'FETCH'", "'FIELDS'", + "'FILTER'", "'FILEFORMAT'", "'FIRST'", "'FOLLOWING'", + "'FOR'", "'FOREIGN'", "'FORMAT'", "'FORMATTED'", "'FROM'", + "'FULL'", "'FUNCTION'", "'FUNCTIONS'", "'GENERATED'", + "'GLOBAL'", "'GRANT'", "'GROUP'", "'GROUPING'", "'HAVING'", + "'HOUR'", "'HOURS'", "'IF'", "'IGNORE'", "'IMPORT'", + "'IN'", "'INCLUDE'", "'INDEX'", "'INDEXES'", "'INNER'", + "'INPATH'", "'INPUTFORMAT'", "'INSERT'", "'INTERSECT'", + "'INTERVAL'", "'INTO'", "'IS'", "'ITEMS'", "'JOIN'", + "'KEYS'", "'LAST'", "'LATERAL'", "'LAZY'", "'LEADING'", + "'LEFT'", "'LIKE'", "'ILIKE'", "'LIMIT'", "'LINES'", + "'LIST'", "'LOAD'", "'LOCAL'", "'LOCATION'", "'LOCK'", + "'LOCKS'", "'LOGICAL'", "'MACRO'", "'MAP'", "'MATCHED'", + "'MERGE'", "'MICROSECOND'", "'MICROSECONDS'", "'MILLISECOND'", + "'MILLISECONDS'", "'MINUTE'", "'MINUTES'", "'MONTH'", + "'MONTHS'", "'MSCK'", "'NAMESPACE'", "'NAMESPACES'", + "'NANOSECOND'", "'NANOSECONDS'", "'NATURAL'", "'NO'", + "", "'NULL'", "'NULLS'", "'OF'", "'OFFSET'", + "'ON'", "'ONLY'", "'OPTION'", "'OPTIONS'", "'OR'", + "'ORDER'", "'OUT'", "'OUTER'", "'OUTPUTFORMAT'", "'OVER'", + "'OVERLAPS'", "'OVERLAY'", "'OVERWRITE'", "'PARTITION'", + "'PARTITIONED'", "'PARTITIONS'", "'PERCENTILE_CONT'", + "'PERCENTILE_DISC'", "'PERCENT'", "'PIVOT'", "'PLACING'", + "'POSITION'", "'PRECEDING'", "'PRIMARY'", "'PRINCIPALS'", + "'PROPERTIES'", "'PURGE'", "'QUARTER'", "'QUERY'", + "'RANGE'", "'RECORDREADER'", "'RECORDWRITER'", "'RECOVER'", + "'REDUCE'", "'REFERENCES'", "'REFRESH'", "'RENAME'", + "'REPAIR'", "'REPEATABLE'", "'REPLACE'", "'RESET'", + "'RESPECT'", "'RESTRICT'", "'REVOKE'", "'RIGHT'", "", + "'ROLE'", "'ROLES'", "'ROLLBACK'", "'ROLLUP'", "'ROW'", + "'ROWS'", "'SECOND'", "'SECONDS'", "'SCHEMA'", "'SCHEMAS'", + "'SELECT'", "'SEMI'", "'SEPARATED'", "'SERDE'", "'SERDEPROPERTIES'", + "'SESSION_USER'", "'SET'", "'MINUS'", "'SETS'", "'SHOW'", + "'SKEWED'", "'SOME'", "'SORT'", "'SORTED'", "'SOURCE'", + "'START'", "'STATISTICS'", "'STORED'", "'STRATIFY'", + "'STRUCT'", "'SUBSTR'", "'SUBSTRING'", "'SYNC'", "'SYSTEM_TIME'", + "'SYSTEM_VERSION'", "'TABLE'", "'TABLES'", "'TABLESAMPLE'", + "'TARGET'", "'TBLPROPERTIES'", "", "'TERMINATED'", + "'THEN'", "'TIME'", "'TIMESTAMP'", "'TIMESTAMPADD'", + "'TIMESTAMPDIFF'", "'TO'", "'TOUCH'", "'TRAILING'", + "'TRANSACTION'", "'TRANSACTIONS'", "'TRANSFORM'", "'TRIM'", + "'TRUE'", "'TRUNCATE'", "'TRY_CAST'", "'TYPE'", "'UNARCHIVE'", + "'UNBOUNDED'", "'UNCACHE'", "'UNION'", "'UNIQUE'", + "'UNKNOWN'", "'UNLOCK'", "'UNPIVOT'", "'UNSET'", "'UPDATE'", + "'USE'", "'USER'", "'USING'", "'VALUES'", "'VERSION'", + "'VIEW'", "'VIEWS'", "'WEEK'", "'WEEKS'", "'WHEN'", + "'WHERE'", "'WINDOW'", "'WITH'", "'WITHIN'", "'YEAR'", + "'YEARS'", "'ZONE'", "", "'<=>'", "'<>'", + "'!='", "'<'", "", "'>'", "", "'+'", + "'-'", "'*'", "'/'", "'%'", "'~'", "'&'", "'|'", "'||'", + "'^'", "':'", "'->'", "'/*+'", "'*/'" ] + + symbolicNames = [ "", "SEMICOLON", "LEFT_PAREN", "RIGHT_PAREN", + "COMMA", "DOT", "LEFT_BRACKET", "RIGHT_BRACKET", "ADD", + "AFTER", "ALL", "ALTER", "ALWAYS", "ANALYZE", "AND", + "ANTI", "ANY", "ANY_VALUE", "ARCHIVE", "ARRAY", "AS", + "ASC", "AT", "AUTHORIZATION", "BETWEEN", "BOTH", "BUCKET", + "BUCKETS", "BY", "CACHE", "CASCADE", "CASE", "CAST", + "CATALOG", "CATALOGS", "CHANGE", "CHECK", "CLEAR", + "CLUSTER", "CLUSTERED", "CODEGEN", "COLLATE", "COLLECTION", + "COLUMN", "COLUMNS", "COMMENT", "COMMIT", "COMPACT", + "COMPACTIONS", "COMPUTE", "CONCATENATE", "CONSTRAINT", + "COST", "CREATE", "CROSS", "CUBE", "CURRENT", "CURRENT_DATE", + "CURRENT_TIME", "CURRENT_TIMESTAMP", "CURRENT_USER", + "DAY", "DAYS", "DAYOFYEAR", "DATA", "DATABASE", "DATABASES", + "DATEADD", "DATEDIFF", "DBPROPERTIES", "DEFAULT", + "DEFINED", "DELETE", "DELIMITED", "DESC", "DESCRIBE", + "DFS", "DIRECTORIES", "DIRECTORY", "DISTINCT", "DISTRIBUTE", + "DIV", "DROP", "ELSE", "END", "ESCAPE", "ESCAPED", + "EXCEPT", "EXCHANGE", "EXCLUDE", "EXISTS", "EXPLAIN", + "EXPORT", "EXTENDED", "EXTERNAL", "EXTRACT", "FALSE", + "FETCH", "FIELDS", "FILTER", "FILEFORMAT", "FIRST", + "FOLLOWING", "FOR", "FOREIGN", "FORMAT", "FORMATTED", + "FROM", "FULL", "FUNCTION", "FUNCTIONS", "GENERATED", + "GLOBAL", "GRANT", "GROUP", "GROUPING", "HAVING", + "HOUR", "HOURS", "IF", "IGNORE", "IMPORT", "IN", "INCLUDE", + "INDEX", "INDEXES", "INNER", "INPATH", "INPUTFORMAT", + "INSERT", "INTERSECT", "INTERVAL", "INTO", "IS", "ITEMS", + "JOIN", "KEYS", "LAST", "LATERAL", "LAZY", "LEADING", + "LEFT", "LIKE", "ILIKE", "LIMIT", "LINES", "LIST", + "LOAD", "LOCAL", "LOCATION", "LOCK", "LOCKS", "LOGICAL", + "MACRO", "MAP", "MATCHED", "MERGE", "MICROSECOND", + "MICROSECONDS", "MILLISECOND", "MILLISECONDS", "MINUTE", + "MINUTES", "MONTH", "MONTHS", "MSCK", "NAMESPACE", + "NAMESPACES", "NANOSECOND", "NANOSECONDS", "NATURAL", + "NO", "NOT", "NULL", "NULLS", "OF", "OFFSET", "ON", + "ONLY", "OPTION", "OPTIONS", "OR", "ORDER", "OUT", + "OUTER", "OUTPUTFORMAT", "OVER", "OVERLAPS", "OVERLAY", + "OVERWRITE", "PARTITION", "PARTITIONED", "PARTITIONS", + "PERCENTILE_CONT", "PERCENTILE_DISC", "PERCENTLIT", + "PIVOT", "PLACING", "POSITION", "PRECEDING", "PRIMARY", + "PRINCIPALS", "PROPERTIES", "PURGE", "QUARTER", "QUERY", + "RANGE", "RECORDREADER", "RECORDWRITER", "RECOVER", + "REDUCE", "REFERENCES", "REFRESH", "RENAME", "REPAIR", + "REPEATABLE", "REPLACE", "RESET", "RESPECT", "RESTRICT", + "REVOKE", "RIGHT", "RLIKE", "ROLE", "ROLES", "ROLLBACK", + "ROLLUP", "ROW", "ROWS", "SECOND", "SECONDS", "SCHEMA", + "SCHEMAS", "SELECT", "SEMI", "SEPARATED", "SERDE", + "SERDEPROPERTIES", "SESSION_USER", "SET", "SETMINUS", + "SETS", "SHOW", "SKEWED", "SOME", "SORT", "SORTED", + "SOURCE", "START", "STATISTICS", "STORED", "STRATIFY", + "STRUCT", "SUBSTR", "SUBSTRING", "SYNC", "SYSTEM_TIME", + "SYSTEM_VERSION", "TABLE", "TABLES", "TABLESAMPLE", + "TARGET", "TBLPROPERTIES", "TEMPORARY", "TERMINATED", + "THEN", "TIME", "TIMESTAMP", "TIMESTAMPADD", "TIMESTAMPDIFF", + "TO", "TOUCH", "TRAILING", "TRANSACTION", "TRANSACTIONS", + "TRANSFORM", "TRIM", "TRUE", "TRUNCATE", "TRY_CAST", + "TYPE", "UNARCHIVE", "UNBOUNDED", "UNCACHE", "UNION", + "UNIQUE", "UNKNOWN", "UNLOCK", "UNPIVOT", "UNSET", + "UPDATE", "USE", "USER", "USING", "VALUES", "VERSION", + "VIEW", "VIEWS", "WEEK", "WEEKS", "WHEN", "WHERE", + "WINDOW", "WITH", "WITHIN", "YEAR", "YEARS", "ZONE", + "EQ", "NSEQ", "NEQ", "NEQJ", "LT", "LTE", "GT", "GTE", + "PLUS", "MINUS", "ASTERISK", "SLASH", "PERCENT", "TILDE", + "AMPERSAND", "PIPE", "CONCAT_PIPE", "HAT", "COLON", + "ARROW", "HENT_START", "HENT_END", "STRING", "DOUBLEQUOTED_STRING", + "BIGINT_LITERAL", "SMALLINT_LITERAL", "TINYINT_LITERAL", + "INTEGER_VALUE", "EXPONENT_VALUE", "DECIMAL_VALUE", + "FLOAT_LITERAL", "DOUBLE_LITERAL", "BIGDECIMAL_LITERAL", + "IDENTIFIER", "BACKQUOTED_IDENTIFIER", "SIMPLE_COMMENT", + "BRACKETED_COMMENT", "WS", "UNRECOGNIZED" ] + + RULE_singleStatement = 0 + RULE_singleExpression = 1 + RULE_singleTableIdentifier = 2 + RULE_singleMultipartIdentifier = 3 + RULE_singleFunctionIdentifier = 4 + RULE_singleDataType = 5 + RULE_singleTableSchema = 6 + RULE_statement = 7 + RULE_timezone = 8 + RULE_configKey = 9 + RULE_configValue = 10 + RULE_unsupportedHiveNativeCommands = 11 + RULE_createTableHeader = 12 + RULE_replaceTableHeader = 13 + RULE_bucketSpec = 14 + RULE_skewSpec = 15 + RULE_locationSpec = 16 + RULE_commentSpec = 17 + RULE_query = 18 + RULE_insertInto = 19 + RULE_partitionSpecLocation = 20 + RULE_partitionSpec = 21 + RULE_partitionVal = 22 + RULE_namespace = 23 + RULE_namespaces = 24 + RULE_describeFuncName = 25 + RULE_describeColName = 26 + RULE_ctes = 27 + RULE_namedQuery = 28 + RULE_tableProvider = 29 + RULE_createTableClauses = 30 + RULE_propertyList = 31 + RULE_property = 32 + RULE_propertyKey = 33 + RULE_propertyValue = 34 + RULE_constantList = 35 + RULE_nestedConstantList = 36 + RULE_createFileFormat = 37 + RULE_fileFormat = 38 + RULE_storageHandler = 39 + RULE_resource = 40 + RULE_dmlStatementNoWith = 41 + RULE_queryOrganization = 42 + RULE_multiInsertQueryBody = 43 + RULE_queryTerm = 44 + RULE_queryPrimary = 45 + RULE_sortItem = 46 + RULE_fromStatement = 47 + RULE_fromStatementBody = 48 + RULE_querySpecification = 49 + RULE_transformClause = 50 + RULE_selectClause = 51 + RULE_setClause = 52 + RULE_matchedClause = 53 + RULE_notMatchedClause = 54 + RULE_notMatchedBySourceClause = 55 + RULE_matchedAction = 56 + RULE_notMatchedAction = 57 + RULE_notMatchedBySourceAction = 58 + RULE_assignmentList = 59 + RULE_assignment = 60 + RULE_whereClause = 61 + RULE_havingClause = 62 + RULE_hint = 63 + RULE_hintStatement = 64 + RULE_fromClause = 65 + RULE_temporalClause = 66 + RULE_aggregationClause = 67 + RULE_groupByClause = 68 + RULE_groupingAnalytics = 69 + RULE_groupingElement = 70 + RULE_groupingSet = 71 + RULE_pivotClause = 72 + RULE_pivotColumn = 73 + RULE_pivotValue = 74 + RULE_unpivotClause = 75 + RULE_unpivotNullClause = 76 + RULE_unpivotOperator = 77 + RULE_unpivotSingleValueColumnClause = 78 + RULE_unpivotMultiValueColumnClause = 79 + RULE_unpivotColumnSet = 80 + RULE_unpivotValueColumn = 81 + RULE_unpivotNameColumn = 82 + RULE_unpivotColumnAndAlias = 83 + RULE_unpivotColumn = 84 + RULE_unpivotAlias = 85 + RULE_lateralView = 86 + RULE_setQuantifier = 87 + RULE_relation = 88 + RULE_relationExtension = 89 + RULE_joinRelation = 90 + RULE_joinType = 91 + RULE_joinCriteria = 92 + RULE_sample = 93 + RULE_sampleMethod = 94 + RULE_identifierList = 95 + RULE_identifierSeq = 96 + RULE_orderedIdentifierList = 97 + RULE_orderedIdentifier = 98 + RULE_identifierCommentList = 99 + RULE_identifierComment = 100 + RULE_relationPrimary = 101 + RULE_inlineTable = 102 + RULE_functionTable = 103 + RULE_tableAlias = 104 + RULE_rowFormat = 105 + RULE_multipartIdentifierList = 106 + RULE_multipartIdentifier = 107 + RULE_multipartIdentifierPropertyList = 108 + RULE_multipartIdentifierProperty = 109 + RULE_tableIdentifier = 110 + RULE_functionIdentifier = 111 + RULE_namedExpression = 112 + RULE_namedExpressionSeq = 113 + RULE_partitionFieldList = 114 + RULE_partitionField = 115 + RULE_transform = 116 + RULE_transformArgument = 117 + RULE_expression = 118 + RULE_expressionSeq = 119 + RULE_booleanExpression = 120 + RULE_predicate = 121 + RULE_valueExpression = 122 + RULE_datetimeUnit = 123 + RULE_primaryExpression = 124 + RULE_constant = 125 + RULE_comparisonOperator = 126 + RULE_arithmeticOperator = 127 + RULE_predicateOperator = 128 + RULE_booleanValue = 129 + RULE_interval = 130 + RULE_errorCapturingMultiUnitsInterval = 131 + RULE_multiUnitsInterval = 132 + RULE_errorCapturingUnitToUnitInterval = 133 + RULE_unitToUnitInterval = 134 + RULE_intervalValue = 135 + RULE_unitInMultiUnits = 136 + RULE_unitInUnitToUnit = 137 + RULE_colPosition = 138 + RULE_dataType = 139 + RULE_qualifiedColTypeWithPositionList = 140 + RULE_qualifiedColTypeWithPosition = 141 + RULE_colDefinitionDescriptorWithPosition = 142 + RULE_defaultExpression = 143 + RULE_colTypeList = 144 + RULE_colType = 145 + RULE_createOrReplaceTableColTypeList = 146 + RULE_createOrReplaceTableColType = 147 + RULE_colDefinitionOption = 148 + RULE_generationExpression = 149 + RULE_complexColTypeList = 150 + RULE_complexColType = 151 + RULE_whenClause = 152 + RULE_windowClause = 153 + RULE_namedWindow = 154 + RULE_windowSpec = 155 + RULE_windowFrame = 156 + RULE_frameBound = 157 + RULE_qualifiedNameList = 158 + RULE_functionName = 159 + RULE_qualifiedName = 160 + RULE_errorCapturingIdentifier = 161 + RULE_errorCapturingIdentifierExtra = 162 + RULE_identifier = 163 + RULE_strictIdentifier = 164 + RULE_quotedIdentifier = 165 + RULE_backQuotedIdentifier = 166 + RULE_number = 167 + RULE_alterColumnAction = 168 + RULE_stringLit = 169 + RULE_comment = 170 + RULE_version = 171 + RULE_ansiNonReserved = 172 + RULE_strictNonReserved = 173 + RULE_nonReserved = 174 + + ruleNames = [ "singleStatement", "singleExpression", "singleTableIdentifier", + "singleMultipartIdentifier", "singleFunctionIdentifier", + "singleDataType", "singleTableSchema", "statement", "timezone", + "configKey", "configValue", "unsupportedHiveNativeCommands", + "createTableHeader", "replaceTableHeader", "bucketSpec", + "skewSpec", "locationSpec", "commentSpec", "query", "insertInto", + "partitionSpecLocation", "partitionSpec", "partitionVal", + "namespace", "namespaces", "describeFuncName", "describeColName", + "ctes", "namedQuery", "tableProvider", "createTableClauses", + "propertyList", "property", "propertyKey", "propertyValue", + "constantList", "nestedConstantList", "createFileFormat", + "fileFormat", "storageHandler", "resource", "dmlStatementNoWith", + "queryOrganization", "multiInsertQueryBody", "queryTerm", + "queryPrimary", "sortItem", "fromStatement", "fromStatementBody", + "querySpecification", "transformClause", "selectClause", + "setClause", "matchedClause", "notMatchedClause", "notMatchedBySourceClause", + "matchedAction", "notMatchedAction", "notMatchedBySourceAction", + "assignmentList", "assignment", "whereClause", "havingClause", + "hint", "hintStatement", "fromClause", "temporalClause", + "aggregationClause", "groupByClause", "groupingAnalytics", + "groupingElement", "groupingSet", "pivotClause", "pivotColumn", + "pivotValue", "unpivotClause", "unpivotNullClause", "unpivotOperator", + "unpivotSingleValueColumnClause", "unpivotMultiValueColumnClause", + "unpivotColumnSet", "unpivotValueColumn", "unpivotNameColumn", + "unpivotColumnAndAlias", "unpivotColumn", "unpivotAlias", + "lateralView", "setQuantifier", "relation", "relationExtension", + "joinRelation", "joinType", "joinCriteria", "sample", + "sampleMethod", "identifierList", "identifierSeq", "orderedIdentifierList", + "orderedIdentifier", "identifierCommentList", "identifierComment", + "relationPrimary", "inlineTable", "functionTable", "tableAlias", + "rowFormat", "multipartIdentifierList", "multipartIdentifier", + "multipartIdentifierPropertyList", "multipartIdentifierProperty", + "tableIdentifier", "functionIdentifier", "namedExpression", + "namedExpressionSeq", "partitionFieldList", "partitionField", + "transform", "transformArgument", "expression", "expressionSeq", + "booleanExpression", "predicate", "valueExpression", + "datetimeUnit", "primaryExpression", "constant", "comparisonOperator", + "arithmeticOperator", "predicateOperator", "booleanValue", + "interval", "errorCapturingMultiUnitsInterval", "multiUnitsInterval", + "errorCapturingUnitToUnitInterval", "unitToUnitInterval", + "intervalValue", "unitInMultiUnits", "unitInUnitToUnit", + "colPosition", "dataType", "qualifiedColTypeWithPositionList", + "qualifiedColTypeWithPosition", "colDefinitionDescriptorWithPosition", + "defaultExpression", "colTypeList", "colType", "createOrReplaceTableColTypeList", + "createOrReplaceTableColType", "colDefinitionOption", + "generationExpression", "complexColTypeList", "complexColType", + "whenClause", "windowClause", "namedWindow", "windowSpec", + "windowFrame", "frameBound", "qualifiedNameList", "functionName", + "qualifiedName", "errorCapturingIdentifier", "errorCapturingIdentifierExtra", + "identifier", "strictIdentifier", "quotedIdentifier", + "backQuotedIdentifier", "number", "alterColumnAction", + "stringLit", "comment", "version", "ansiNonReserved", + "strictNonReserved", "nonReserved" ] + + EOF = Token.EOF + SEMICOLON=1 + LEFT_PAREN=2 + RIGHT_PAREN=3 + COMMA=4 + DOT=5 + LEFT_BRACKET=6 + RIGHT_BRACKET=7 + ADD=8 + AFTER=9 + ALL=10 + ALTER=11 + ALWAYS=12 + ANALYZE=13 + AND=14 + ANTI=15 + ANY=16 + ANY_VALUE=17 + ARCHIVE=18 + ARRAY=19 + AS=20 + ASC=21 + AT=22 + AUTHORIZATION=23 + BETWEEN=24 + BOTH=25 + BUCKET=26 + BUCKETS=27 + BY=28 + CACHE=29 + CASCADE=30 + CASE=31 + CAST=32 + CATALOG=33 + CATALOGS=34 + CHANGE=35 + CHECK=36 + CLEAR=37 + CLUSTER=38 + CLUSTERED=39 + CODEGEN=40 + COLLATE=41 + COLLECTION=42 + COLUMN=43 + COLUMNS=44 + COMMENT=45 + COMMIT=46 + COMPACT=47 + COMPACTIONS=48 + COMPUTE=49 + CONCATENATE=50 + CONSTRAINT=51 + COST=52 + CREATE=53 + CROSS=54 + CUBE=55 + CURRENT=56 + CURRENT_DATE=57 + CURRENT_TIME=58 + CURRENT_TIMESTAMP=59 + CURRENT_USER=60 + DAY=61 + DAYS=62 + DAYOFYEAR=63 + DATA=64 + DATABASE=65 + DATABASES=66 + DATEADD=67 + DATEDIFF=68 + DBPROPERTIES=69 + DEFAULT=70 + DEFINED=71 + DELETE=72 + DELIMITED=73 + DESC=74 + DESCRIBE=75 + DFS=76 + DIRECTORIES=77 + DIRECTORY=78 + DISTINCT=79 + DISTRIBUTE=80 + DIV=81 + DROP=82 + ELSE=83 + END=84 + ESCAPE=85 + ESCAPED=86 + EXCEPT=87 + EXCHANGE=88 + EXCLUDE=89 + EXISTS=90 + EXPLAIN=91 + EXPORT=92 + EXTENDED=93 + EXTERNAL=94 + EXTRACT=95 + FALSE=96 + FETCH=97 + FIELDS=98 + FILTER=99 + FILEFORMAT=100 + FIRST=101 + FOLLOWING=102 + FOR=103 + FOREIGN=104 + FORMAT=105 + FORMATTED=106 + FROM=107 + FULL=108 + FUNCTION=109 + FUNCTIONS=110 + GENERATED=111 + GLOBAL=112 + GRANT=113 + GROUP=114 + GROUPING=115 + HAVING=116 + HOUR=117 + HOURS=118 + IF=119 + IGNORE=120 + IMPORT=121 + IN=122 + INCLUDE=123 + INDEX=124 + INDEXES=125 + INNER=126 + INPATH=127 + INPUTFORMAT=128 + INSERT=129 + INTERSECT=130 + INTERVAL=131 + INTO=132 + IS=133 + ITEMS=134 + JOIN=135 + KEYS=136 + LAST=137 + LATERAL=138 + LAZY=139 + LEADING=140 + LEFT=141 + LIKE=142 + ILIKE=143 + LIMIT=144 + LINES=145 + LIST=146 + LOAD=147 + LOCAL=148 + LOCATION=149 + LOCK=150 + LOCKS=151 + LOGICAL=152 + MACRO=153 + MAP=154 + MATCHED=155 + MERGE=156 + MICROSECOND=157 + MICROSECONDS=158 + MILLISECOND=159 + MILLISECONDS=160 + MINUTE=161 + MINUTES=162 + MONTH=163 + MONTHS=164 + MSCK=165 + NAMESPACE=166 + NAMESPACES=167 + NANOSECOND=168 + NANOSECONDS=169 + NATURAL=170 + NO=171 + NOT=172 + NULL=173 + NULLS=174 + OF=175 + OFFSET=176 + ON=177 + ONLY=178 + OPTION=179 + OPTIONS=180 + OR=181 + ORDER=182 + OUT=183 + OUTER=184 + OUTPUTFORMAT=185 + OVER=186 + OVERLAPS=187 + OVERLAY=188 + OVERWRITE=189 + PARTITION=190 + PARTITIONED=191 + PARTITIONS=192 + PERCENTILE_CONT=193 + PERCENTILE_DISC=194 + PERCENTLIT=195 + PIVOT=196 + PLACING=197 + POSITION=198 + PRECEDING=199 + PRIMARY=200 + PRINCIPALS=201 + PROPERTIES=202 + PURGE=203 + QUARTER=204 + QUERY=205 + RANGE=206 + RECORDREADER=207 + RECORDWRITER=208 + RECOVER=209 + REDUCE=210 + REFERENCES=211 + REFRESH=212 + RENAME=213 + REPAIR=214 + REPEATABLE=215 + REPLACE=216 + RESET=217 + RESPECT=218 + RESTRICT=219 + REVOKE=220 + RIGHT=221 + RLIKE=222 + ROLE=223 + ROLES=224 + ROLLBACK=225 + ROLLUP=226 + ROW=227 + ROWS=228 + SECOND=229 + SECONDS=230 + SCHEMA=231 + SCHEMAS=232 + SELECT=233 + SEMI=234 + SEPARATED=235 + SERDE=236 + SERDEPROPERTIES=237 + SESSION_USER=238 + SET=239 + SETMINUS=240 + SETS=241 + SHOW=242 + SKEWED=243 + SOME=244 + SORT=245 + SORTED=246 + SOURCE=247 + START=248 + STATISTICS=249 + STORED=250 + STRATIFY=251 + STRUCT=252 + SUBSTR=253 + SUBSTRING=254 + SYNC=255 + SYSTEM_TIME=256 + SYSTEM_VERSION=257 + TABLE=258 + TABLES=259 + TABLESAMPLE=260 + TARGET=261 + TBLPROPERTIES=262 + TEMPORARY=263 + TERMINATED=264 + THEN=265 + TIME=266 + TIMESTAMP=267 + TIMESTAMPADD=268 + TIMESTAMPDIFF=269 + TO=270 + TOUCH=271 + TRAILING=272 + TRANSACTION=273 + TRANSACTIONS=274 + TRANSFORM=275 + TRIM=276 + TRUE=277 + TRUNCATE=278 + TRY_CAST=279 + TYPE=280 + UNARCHIVE=281 + UNBOUNDED=282 + UNCACHE=283 + UNION=284 + UNIQUE=285 + UNKNOWN=286 + UNLOCK=287 + UNPIVOT=288 + UNSET=289 + UPDATE=290 + USE=291 + USER=292 + USING=293 + VALUES=294 + VERSION=295 + VIEW=296 + VIEWS=297 + WEEK=298 + WEEKS=299 + WHEN=300 + WHERE=301 + WINDOW=302 + WITH=303 + WITHIN=304 + YEAR=305 + YEARS=306 + ZONE=307 + EQ=308 + NSEQ=309 + NEQ=310 + NEQJ=311 + LT=312 + LTE=313 + GT=314 + GTE=315 + PLUS=316 + MINUS=317 + ASTERISK=318 + SLASH=319 + PERCENT=320 + TILDE=321 + AMPERSAND=322 + PIPE=323 + CONCAT_PIPE=324 + HAT=325 + COLON=326 + ARROW=327 + HENT_START=328 + HENT_END=329 + STRING=330 + DOUBLEQUOTED_STRING=331 + BIGINT_LITERAL=332 + SMALLINT_LITERAL=333 + TINYINT_LITERAL=334 + INTEGER_VALUE=335 + EXPONENT_VALUE=336 + DECIMAL_VALUE=337 + FLOAT_LITERAL=338 + DOUBLE_LITERAL=339 + BIGDECIMAL_LITERAL=340 + IDENTIFIER=341 + BACKQUOTED_IDENTIFIER=342 + SIMPLE_COMMENT=343 + BRACKETED_COMMENT=344 + WS=345 + UNRECOGNIZED=346 + + def __init__(self, input:TokenStream, output:TextIO = sys.stdout): + super().__init__(input, output) + self.checkVersion("4.12.0") + self._interp = ParserATNSimulator(self, self.atn, self.decisionsToDFA, self.sharedContextCache) + self._predicates = None + + + + + class SingleStatementContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def statement(self): + return self.getTypedRuleContext(SqlBaseParser.StatementContext,0) + + + def EOF(self): + return self.getToken(SqlBaseParser.EOF, 0) + + def SEMICOLON(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.SEMICOLON) + else: + return self.getToken(SqlBaseParser.SEMICOLON, i) + + def getRuleIndex(self): + return SqlBaseParser.RULE_singleStatement + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSingleStatement" ): + listener.enterSingleStatement(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSingleStatement" ): + listener.exitSingleStatement(self) + + + + + def singleStatement(self): + + localctx = SqlBaseParser.SingleStatementContext(self, self._ctx, self.state) + self.enterRule(localctx, 0, self.RULE_singleStatement) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 350 + self.statement() + self.state = 354 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==1: + self.state = 351 + self.match(SqlBaseParser.SEMICOLON) + self.state = 356 + self._errHandler.sync(self) + _la = self._input.LA(1) + + self.state = 357 + self.match(SqlBaseParser.EOF) + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class SingleExpressionContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def namedExpression(self): + return self.getTypedRuleContext(SqlBaseParser.NamedExpressionContext,0) + + + def EOF(self): + return self.getToken(SqlBaseParser.EOF, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_singleExpression + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSingleExpression" ): + listener.enterSingleExpression(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSingleExpression" ): + listener.exitSingleExpression(self) + + + + + def singleExpression(self): + + localctx = SqlBaseParser.SingleExpressionContext(self, self._ctx, self.state) + self.enterRule(localctx, 2, self.RULE_singleExpression) + try: + self.enterOuterAlt(localctx, 1) + self.state = 359 + self.namedExpression() + self.state = 360 + self.match(SqlBaseParser.EOF) + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class SingleTableIdentifierContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def tableIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.TableIdentifierContext,0) + + + def EOF(self): + return self.getToken(SqlBaseParser.EOF, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_singleTableIdentifier + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSingleTableIdentifier" ): + listener.enterSingleTableIdentifier(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSingleTableIdentifier" ): + listener.exitSingleTableIdentifier(self) + + + + + def singleTableIdentifier(self): + + localctx = SqlBaseParser.SingleTableIdentifierContext(self, self._ctx, self.state) + self.enterRule(localctx, 4, self.RULE_singleTableIdentifier) + try: + self.enterOuterAlt(localctx, 1) + self.state = 362 + self.tableIdentifier() + self.state = 363 + self.match(SqlBaseParser.EOF) + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class SingleMultipartIdentifierContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + + def EOF(self): + return self.getToken(SqlBaseParser.EOF, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_singleMultipartIdentifier + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSingleMultipartIdentifier" ): + listener.enterSingleMultipartIdentifier(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSingleMultipartIdentifier" ): + listener.exitSingleMultipartIdentifier(self) + + + + + def singleMultipartIdentifier(self): + + localctx = SqlBaseParser.SingleMultipartIdentifierContext(self, self._ctx, self.state) + self.enterRule(localctx, 6, self.RULE_singleMultipartIdentifier) + try: + self.enterOuterAlt(localctx, 1) + self.state = 365 + self.multipartIdentifier() + self.state = 366 + self.match(SqlBaseParser.EOF) + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class SingleFunctionIdentifierContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def functionIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.FunctionIdentifierContext,0) + + + def EOF(self): + return self.getToken(SqlBaseParser.EOF, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_singleFunctionIdentifier + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSingleFunctionIdentifier" ): + listener.enterSingleFunctionIdentifier(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSingleFunctionIdentifier" ): + listener.exitSingleFunctionIdentifier(self) + + + + + def singleFunctionIdentifier(self): + + localctx = SqlBaseParser.SingleFunctionIdentifierContext(self, self._ctx, self.state) + self.enterRule(localctx, 8, self.RULE_singleFunctionIdentifier) + try: + self.enterOuterAlt(localctx, 1) + self.state = 368 + self.functionIdentifier() + self.state = 369 + self.match(SqlBaseParser.EOF) + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class SingleDataTypeContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def dataType(self): + return self.getTypedRuleContext(SqlBaseParser.DataTypeContext,0) + + + def EOF(self): + return self.getToken(SqlBaseParser.EOF, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_singleDataType + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSingleDataType" ): + listener.enterSingleDataType(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSingleDataType" ): + listener.exitSingleDataType(self) + + + + + def singleDataType(self): + + localctx = SqlBaseParser.SingleDataTypeContext(self, self._ctx, self.state) + self.enterRule(localctx, 10, self.RULE_singleDataType) + try: + self.enterOuterAlt(localctx, 1) + self.state = 371 + self.dataType() + self.state = 372 + self.match(SqlBaseParser.EOF) + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class SingleTableSchemaContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def colTypeList(self): + return self.getTypedRuleContext(SqlBaseParser.ColTypeListContext,0) + + + def EOF(self): + return self.getToken(SqlBaseParser.EOF, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_singleTableSchema + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSingleTableSchema" ): + listener.enterSingleTableSchema(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSingleTableSchema" ): + listener.exitSingleTableSchema(self) + + + + + def singleTableSchema(self): + + localctx = SqlBaseParser.SingleTableSchemaContext(self, self._ctx, self.state) + self.enterRule(localctx, 12, self.RULE_singleTableSchema) + try: + self.enterOuterAlt(localctx, 1) + self.state = 374 + self.colTypeList() + self.state = 375 + self.match(SqlBaseParser.EOF) + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class StatementContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + + def getRuleIndex(self): + return SqlBaseParser.RULE_statement + + + def copyFrom(self, ctx:ParserRuleContext): + super().copyFrom(ctx) + + + + class ExplainContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def EXPLAIN(self): + return self.getToken(SqlBaseParser.EXPLAIN, 0) + def statement(self): + return self.getTypedRuleContext(SqlBaseParser.StatementContext,0) + + def LOGICAL(self): + return self.getToken(SqlBaseParser.LOGICAL, 0) + def FORMATTED(self): + return self.getToken(SqlBaseParser.FORMATTED, 0) + def EXTENDED(self): + return self.getToken(SqlBaseParser.EXTENDED, 0) + def CODEGEN(self): + return self.getToken(SqlBaseParser.CODEGEN, 0) + def COST(self): + return self.getToken(SqlBaseParser.COST, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterExplain" ): + listener.enterExplain(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitExplain" ): + listener.exitExplain(self) + + + class ResetConfigurationContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def RESET(self): + return self.getToken(SqlBaseParser.RESET, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterResetConfiguration" ): + listener.enterResetConfiguration(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitResetConfiguration" ): + listener.exitResetConfiguration(self) + + + class AlterViewQueryContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def ALTER(self): + return self.getToken(SqlBaseParser.ALTER, 0) + def VIEW(self): + return self.getToken(SqlBaseParser.VIEW, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def query(self): + return self.getTypedRuleContext(SqlBaseParser.QueryContext,0) + + def AS(self): + return self.getToken(SqlBaseParser.AS, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterAlterViewQuery" ): + listener.enterAlterViewQuery(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitAlterViewQuery" ): + listener.exitAlterViewQuery(self) + + + class UseContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def USE(self): + return self.getToken(SqlBaseParser.USE, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterUse" ): + listener.enterUse(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitUse" ): + listener.exitUse(self) + + + class DropNamespaceContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def DROP(self): + return self.getToken(SqlBaseParser.DROP, 0) + def namespace(self): + return self.getTypedRuleContext(SqlBaseParser.NamespaceContext,0) + + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def IF(self): + return self.getToken(SqlBaseParser.IF, 0) + def EXISTS(self): + return self.getToken(SqlBaseParser.EXISTS, 0) + def RESTRICT(self): + return self.getToken(SqlBaseParser.RESTRICT, 0) + def CASCADE(self): + return self.getToken(SqlBaseParser.CASCADE, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterDropNamespace" ): + listener.enterDropNamespace(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitDropNamespace" ): + listener.exitDropNamespace(self) + + + class CreateTempViewUsingContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def CREATE(self): + return self.getToken(SqlBaseParser.CREATE, 0) + def TEMPORARY(self): + return self.getToken(SqlBaseParser.TEMPORARY, 0) + def VIEW(self): + return self.getToken(SqlBaseParser.VIEW, 0) + def tableIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.TableIdentifierContext,0) + + def tableProvider(self): + return self.getTypedRuleContext(SqlBaseParser.TableProviderContext,0) + + def OR(self): + return self.getToken(SqlBaseParser.OR, 0) + def REPLACE(self): + return self.getToken(SqlBaseParser.REPLACE, 0) + def GLOBAL(self): + return self.getToken(SqlBaseParser.GLOBAL, 0) + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + def colTypeList(self): + return self.getTypedRuleContext(SqlBaseParser.ColTypeListContext,0) + + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + def OPTIONS(self): + return self.getToken(SqlBaseParser.OPTIONS, 0) + def propertyList(self): + return self.getTypedRuleContext(SqlBaseParser.PropertyListContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterCreateTempViewUsing" ): + listener.enterCreateTempViewUsing(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitCreateTempViewUsing" ): + listener.exitCreateTempViewUsing(self) + + + class RenameTableContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.from_ = None # MultipartIdentifierContext + self.to = None # MultipartIdentifierContext + self.copyFrom(ctx) + + def ALTER(self): + return self.getToken(SqlBaseParser.ALTER, 0) + def RENAME(self): + return self.getToken(SqlBaseParser.RENAME, 0) + def TO(self): + return self.getToken(SqlBaseParser.TO, 0) + def TABLE(self): + return self.getToken(SqlBaseParser.TABLE, 0) + def VIEW(self): + return self.getToken(SqlBaseParser.VIEW, 0) + def multipartIdentifier(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.MultipartIdentifierContext) + else: + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,i) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterRenameTable" ): + listener.enterRenameTable(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitRenameTable" ): + listener.exitRenameTable(self) + + + class FailNativeCommandContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def SET(self): + return self.getToken(SqlBaseParser.SET, 0) + def ROLE(self): + return self.getToken(SqlBaseParser.ROLE, 0) + def unsupportedHiveNativeCommands(self): + return self.getTypedRuleContext(SqlBaseParser.UnsupportedHiveNativeCommandsContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterFailNativeCommand" ): + listener.enterFailNativeCommand(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitFailNativeCommand" ): + listener.exitFailNativeCommand(self) + + + class SetCatalogContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def SET(self): + return self.getToken(SqlBaseParser.SET, 0) + def CATALOG(self): + return self.getToken(SqlBaseParser.CATALOG, 0) + def identifier(self): + return self.getTypedRuleContext(SqlBaseParser.IdentifierContext,0) + + def stringLit(self): + return self.getTypedRuleContext(SqlBaseParser.StringLitContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSetCatalog" ): + listener.enterSetCatalog(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSetCatalog" ): + listener.exitSetCatalog(self) + + + class ClearCacheContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def CLEAR(self): + return self.getToken(SqlBaseParser.CLEAR, 0) + def CACHE(self): + return self.getToken(SqlBaseParser.CACHE, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterClearCache" ): + listener.enterClearCache(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitClearCache" ): + listener.exitClearCache(self) + + + class DropViewContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def DROP(self): + return self.getToken(SqlBaseParser.DROP, 0) + def VIEW(self): + return self.getToken(SqlBaseParser.VIEW, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def IF(self): + return self.getToken(SqlBaseParser.IF, 0) + def EXISTS(self): + return self.getToken(SqlBaseParser.EXISTS, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterDropView" ): + listener.enterDropView(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitDropView" ): + listener.exitDropView(self) + + + class ShowTablesContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.pattern = None # StringLitContext + self.copyFrom(ctx) + + def SHOW(self): + return self.getToken(SqlBaseParser.SHOW, 0) + def TABLES(self): + return self.getToken(SqlBaseParser.TABLES, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def FROM(self): + return self.getToken(SqlBaseParser.FROM, 0) + def IN(self): + return self.getToken(SqlBaseParser.IN, 0) + def stringLit(self): + return self.getTypedRuleContext(SqlBaseParser.StringLitContext,0) + + def LIKE(self): + return self.getToken(SqlBaseParser.LIKE, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterShowTables" ): + listener.enterShowTables(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitShowTables" ): + listener.exitShowTables(self) + + + class RecoverPartitionsContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def ALTER(self): + return self.getToken(SqlBaseParser.ALTER, 0) + def TABLE(self): + return self.getToken(SqlBaseParser.TABLE, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def RECOVER(self): + return self.getToken(SqlBaseParser.RECOVER, 0) + def PARTITIONS(self): + return self.getToken(SqlBaseParser.PARTITIONS, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterRecoverPartitions" ): + listener.enterRecoverPartitions(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitRecoverPartitions" ): + listener.exitRecoverPartitions(self) + + + class DropIndexContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def DROP(self): + return self.getToken(SqlBaseParser.DROP, 0) + def INDEX(self): + return self.getToken(SqlBaseParser.INDEX, 0) + def identifier(self): + return self.getTypedRuleContext(SqlBaseParser.IdentifierContext,0) + + def ON(self): + return self.getToken(SqlBaseParser.ON, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def IF(self): + return self.getToken(SqlBaseParser.IF, 0) + def EXISTS(self): + return self.getToken(SqlBaseParser.EXISTS, 0) + def TABLE(self): + return self.getToken(SqlBaseParser.TABLE, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterDropIndex" ): + listener.enterDropIndex(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitDropIndex" ): + listener.exitDropIndex(self) + + + class ShowCatalogsContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.pattern = None # StringLitContext + self.copyFrom(ctx) + + def SHOW(self): + return self.getToken(SqlBaseParser.SHOW, 0) + def CATALOGS(self): + return self.getToken(SqlBaseParser.CATALOGS, 0) + def stringLit(self): + return self.getTypedRuleContext(SqlBaseParser.StringLitContext,0) + + def LIKE(self): + return self.getToken(SqlBaseParser.LIKE, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterShowCatalogs" ): + listener.enterShowCatalogs(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitShowCatalogs" ): + listener.exitShowCatalogs(self) + + + class ShowCurrentNamespaceContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def SHOW(self): + return self.getToken(SqlBaseParser.SHOW, 0) + def CURRENT(self): + return self.getToken(SqlBaseParser.CURRENT, 0) + def namespace(self): + return self.getTypedRuleContext(SqlBaseParser.NamespaceContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterShowCurrentNamespace" ): + listener.enterShowCurrentNamespace(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitShowCurrentNamespace" ): + listener.exitShowCurrentNamespace(self) + + + class RenameTablePartitionContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.from_ = None # PartitionSpecContext + self.to = None # PartitionSpecContext + self.copyFrom(ctx) + + def ALTER(self): + return self.getToken(SqlBaseParser.ALTER, 0) + def TABLE(self): + return self.getToken(SqlBaseParser.TABLE, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def RENAME(self): + return self.getToken(SqlBaseParser.RENAME, 0) + def TO(self): + return self.getToken(SqlBaseParser.TO, 0) + def partitionSpec(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.PartitionSpecContext) + else: + return self.getTypedRuleContext(SqlBaseParser.PartitionSpecContext,i) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterRenameTablePartition" ): + listener.enterRenameTablePartition(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitRenameTablePartition" ): + listener.exitRenameTablePartition(self) + + + class RepairTableContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.option = None # Token + self.copyFrom(ctx) + + def REPAIR(self): + return self.getToken(SqlBaseParser.REPAIR, 0) + def TABLE(self): + return self.getToken(SqlBaseParser.TABLE, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def MSCK(self): + return self.getToken(SqlBaseParser.MSCK, 0) + def PARTITIONS(self): + return self.getToken(SqlBaseParser.PARTITIONS, 0) + def ADD(self): + return self.getToken(SqlBaseParser.ADD, 0) + def DROP(self): + return self.getToken(SqlBaseParser.DROP, 0) + def SYNC(self): + return self.getToken(SqlBaseParser.SYNC, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterRepairTable" ): + listener.enterRepairTable(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitRepairTable" ): + listener.exitRepairTable(self) + + + class RefreshResourceContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def REFRESH(self): + return self.getToken(SqlBaseParser.REFRESH, 0) + def stringLit(self): + return self.getTypedRuleContext(SqlBaseParser.StringLitContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterRefreshResource" ): + listener.enterRefreshResource(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitRefreshResource" ): + listener.exitRefreshResource(self) + + + class ShowCreateTableContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def SHOW(self): + return self.getToken(SqlBaseParser.SHOW, 0) + def CREATE(self): + return self.getToken(SqlBaseParser.CREATE, 0) + def TABLE(self): + return self.getToken(SqlBaseParser.TABLE, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def AS(self): + return self.getToken(SqlBaseParser.AS, 0) + def SERDE(self): + return self.getToken(SqlBaseParser.SERDE, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterShowCreateTable" ): + listener.enterShowCreateTable(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitShowCreateTable" ): + listener.exitShowCreateTable(self) + + + class ShowNamespacesContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.pattern = None # StringLitContext + self.copyFrom(ctx) + + def SHOW(self): + return self.getToken(SqlBaseParser.SHOW, 0) + def namespaces(self): + return self.getTypedRuleContext(SqlBaseParser.NamespacesContext,0) + + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def FROM(self): + return self.getToken(SqlBaseParser.FROM, 0) + def IN(self): + return self.getToken(SqlBaseParser.IN, 0) + def stringLit(self): + return self.getTypedRuleContext(SqlBaseParser.StringLitContext,0) + + def LIKE(self): + return self.getToken(SqlBaseParser.LIKE, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterShowNamespaces" ): + listener.enterShowNamespaces(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitShowNamespaces" ): + listener.exitShowNamespaces(self) + + + class ShowColumnsContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.table = None # MultipartIdentifierContext + self.ns = None # MultipartIdentifierContext + self.copyFrom(ctx) + + def SHOW(self): + return self.getToken(SqlBaseParser.SHOW, 0) + def COLUMNS(self): + return self.getToken(SqlBaseParser.COLUMNS, 0) + def FROM(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.FROM) + else: + return self.getToken(SqlBaseParser.FROM, i) + def IN(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.IN) + else: + return self.getToken(SqlBaseParser.IN, i) + def multipartIdentifier(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.MultipartIdentifierContext) + else: + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,i) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterShowColumns" ): + listener.enterShowColumns(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitShowColumns" ): + listener.exitShowColumns(self) + + + class ReplaceTableContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def replaceTableHeader(self): + return self.getTypedRuleContext(SqlBaseParser.ReplaceTableHeaderContext,0) + + def createTableClauses(self): + return self.getTypedRuleContext(SqlBaseParser.CreateTableClausesContext,0) + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + def createOrReplaceTableColTypeList(self): + return self.getTypedRuleContext(SqlBaseParser.CreateOrReplaceTableColTypeListContext,0) + + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + def tableProvider(self): + return self.getTypedRuleContext(SqlBaseParser.TableProviderContext,0) + + def query(self): + return self.getTypedRuleContext(SqlBaseParser.QueryContext,0) + + def AS(self): + return self.getToken(SqlBaseParser.AS, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterReplaceTable" ): + listener.enterReplaceTable(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitReplaceTable" ): + listener.exitReplaceTable(self) + + + class AnalyzeTablesContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def ANALYZE(self): + return self.getToken(SqlBaseParser.ANALYZE, 0) + def TABLES(self): + return self.getToken(SqlBaseParser.TABLES, 0) + def COMPUTE(self): + return self.getToken(SqlBaseParser.COMPUTE, 0) + def STATISTICS(self): + return self.getToken(SqlBaseParser.STATISTICS, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def identifier(self): + return self.getTypedRuleContext(SqlBaseParser.IdentifierContext,0) + + def FROM(self): + return self.getToken(SqlBaseParser.FROM, 0) + def IN(self): + return self.getToken(SqlBaseParser.IN, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterAnalyzeTables" ): + listener.enterAnalyzeTables(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitAnalyzeTables" ): + listener.exitAnalyzeTables(self) + + + class AddTablePartitionContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def ALTER(self): + return self.getToken(SqlBaseParser.ALTER, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def ADD(self): + return self.getToken(SqlBaseParser.ADD, 0) + def TABLE(self): + return self.getToken(SqlBaseParser.TABLE, 0) + def VIEW(self): + return self.getToken(SqlBaseParser.VIEW, 0) + def IF(self): + return self.getToken(SqlBaseParser.IF, 0) + def NOT(self): + return self.getToken(SqlBaseParser.NOT, 0) + def EXISTS(self): + return self.getToken(SqlBaseParser.EXISTS, 0) + def partitionSpecLocation(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.PartitionSpecLocationContext) + else: + return self.getTypedRuleContext(SqlBaseParser.PartitionSpecLocationContext,i) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterAddTablePartition" ): + listener.enterAddTablePartition(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitAddTablePartition" ): + listener.exitAddTablePartition(self) + + + class SetNamespaceLocationContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def ALTER(self): + return self.getToken(SqlBaseParser.ALTER, 0) + def namespace(self): + return self.getTypedRuleContext(SqlBaseParser.NamespaceContext,0) + + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def SET(self): + return self.getToken(SqlBaseParser.SET, 0) + def locationSpec(self): + return self.getTypedRuleContext(SqlBaseParser.LocationSpecContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSetNamespaceLocation" ): + listener.enterSetNamespaceLocation(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSetNamespaceLocation" ): + listener.exitSetNamespaceLocation(self) + + + class RefreshTableContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def REFRESH(self): + return self.getToken(SqlBaseParser.REFRESH, 0) + def TABLE(self): + return self.getToken(SqlBaseParser.TABLE, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterRefreshTable" ): + listener.enterRefreshTable(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitRefreshTable" ): + listener.exitRefreshTable(self) + + + class SetNamespacePropertiesContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def ALTER(self): + return self.getToken(SqlBaseParser.ALTER, 0) + def namespace(self): + return self.getTypedRuleContext(SqlBaseParser.NamespaceContext,0) + + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def SET(self): + return self.getToken(SqlBaseParser.SET, 0) + def propertyList(self): + return self.getTypedRuleContext(SqlBaseParser.PropertyListContext,0) + + def DBPROPERTIES(self): + return self.getToken(SqlBaseParser.DBPROPERTIES, 0) + def PROPERTIES(self): + return self.getToken(SqlBaseParser.PROPERTIES, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSetNamespaceProperties" ): + listener.enterSetNamespaceProperties(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSetNamespaceProperties" ): + listener.exitSetNamespaceProperties(self) + + + class ManageResourceContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.op = None # Token + self.copyFrom(ctx) + + def identifier(self): + return self.getTypedRuleContext(SqlBaseParser.IdentifierContext,0) + + def ADD(self): + return self.getToken(SqlBaseParser.ADD, 0) + def LIST(self): + return self.getToken(SqlBaseParser.LIST, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterManageResource" ): + listener.enterManageResource(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitManageResource" ): + listener.exitManageResource(self) + + + class SetQuotedConfigurationContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def SET(self): + return self.getToken(SqlBaseParser.SET, 0) + def configKey(self): + return self.getTypedRuleContext(SqlBaseParser.ConfigKeyContext,0) + + def EQ(self): + return self.getToken(SqlBaseParser.EQ, 0) + def configValue(self): + return self.getTypedRuleContext(SqlBaseParser.ConfigValueContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSetQuotedConfiguration" ): + listener.enterSetQuotedConfiguration(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSetQuotedConfiguration" ): + listener.exitSetQuotedConfiguration(self) + + + class AnalyzeContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def ANALYZE(self): + return self.getToken(SqlBaseParser.ANALYZE, 0) + def TABLE(self): + return self.getToken(SqlBaseParser.TABLE, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def COMPUTE(self): + return self.getToken(SqlBaseParser.COMPUTE, 0) + def STATISTICS(self): + return self.getToken(SqlBaseParser.STATISTICS, 0) + def partitionSpec(self): + return self.getTypedRuleContext(SqlBaseParser.PartitionSpecContext,0) + + def identifier(self): + return self.getTypedRuleContext(SqlBaseParser.IdentifierContext,0) + + def FOR(self): + return self.getToken(SqlBaseParser.FOR, 0) + def COLUMNS(self): + return self.getToken(SqlBaseParser.COLUMNS, 0) + def identifierSeq(self): + return self.getTypedRuleContext(SqlBaseParser.IdentifierSeqContext,0) + + def ALL(self): + return self.getToken(SqlBaseParser.ALL, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterAnalyze" ): + listener.enterAnalyze(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitAnalyze" ): + listener.exitAnalyze(self) + + + class CreateFunctionContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.className = None # StringLitContext + self.copyFrom(ctx) + + def CREATE(self): + return self.getToken(SqlBaseParser.CREATE, 0) + def FUNCTION(self): + return self.getToken(SqlBaseParser.FUNCTION, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def AS(self): + return self.getToken(SqlBaseParser.AS, 0) + def stringLit(self): + return self.getTypedRuleContext(SqlBaseParser.StringLitContext,0) + + def OR(self): + return self.getToken(SqlBaseParser.OR, 0) + def REPLACE(self): + return self.getToken(SqlBaseParser.REPLACE, 0) + def TEMPORARY(self): + return self.getToken(SqlBaseParser.TEMPORARY, 0) + def IF(self): + return self.getToken(SqlBaseParser.IF, 0) + def NOT(self): + return self.getToken(SqlBaseParser.NOT, 0) + def EXISTS(self): + return self.getToken(SqlBaseParser.EXISTS, 0) + def USING(self): + return self.getToken(SqlBaseParser.USING, 0) + def resource(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.ResourceContext) + else: + return self.getTypedRuleContext(SqlBaseParser.ResourceContext,i) + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterCreateFunction" ): + listener.enterCreateFunction(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitCreateFunction" ): + listener.exitCreateFunction(self) + + + class HiveReplaceColumnsContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.table = None # MultipartIdentifierContext + self.columns = None # QualifiedColTypeWithPositionListContext + self.copyFrom(ctx) + + def ALTER(self): + return self.getToken(SqlBaseParser.ALTER, 0) + def TABLE(self): + return self.getToken(SqlBaseParser.TABLE, 0) + def REPLACE(self): + return self.getToken(SqlBaseParser.REPLACE, 0) + def COLUMNS(self): + return self.getToken(SqlBaseParser.COLUMNS, 0) + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def qualifiedColTypeWithPositionList(self): + return self.getTypedRuleContext(SqlBaseParser.QualifiedColTypeWithPositionListContext,0) + + def partitionSpec(self): + return self.getTypedRuleContext(SqlBaseParser.PartitionSpecContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterHiveReplaceColumns" ): + listener.enterHiveReplaceColumns(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitHiveReplaceColumns" ): + listener.exitHiveReplaceColumns(self) + + + class CommentNamespaceContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def COMMENT(self): + return self.getToken(SqlBaseParser.COMMENT, 0) + def ON(self): + return self.getToken(SqlBaseParser.ON, 0) + def namespace(self): + return self.getTypedRuleContext(SqlBaseParser.NamespaceContext,0) + + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def IS(self): + return self.getToken(SqlBaseParser.IS, 0) + def comment(self): + return self.getTypedRuleContext(SqlBaseParser.CommentContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterCommentNamespace" ): + listener.enterCommentNamespace(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitCommentNamespace" ): + listener.exitCommentNamespace(self) + + + class ResetQuotedConfigurationContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def RESET(self): + return self.getToken(SqlBaseParser.RESET, 0) + def configKey(self): + return self.getTypedRuleContext(SqlBaseParser.ConfigKeyContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterResetQuotedConfiguration" ): + listener.enterResetQuotedConfiguration(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitResetQuotedConfiguration" ): + listener.exitResetQuotedConfiguration(self) + + + class CreateTableContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def createTableHeader(self): + return self.getTypedRuleContext(SqlBaseParser.CreateTableHeaderContext,0) + + def createTableClauses(self): + return self.getTypedRuleContext(SqlBaseParser.CreateTableClausesContext,0) + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + def createOrReplaceTableColTypeList(self): + return self.getTypedRuleContext(SqlBaseParser.CreateOrReplaceTableColTypeListContext,0) + + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + def tableProvider(self): + return self.getTypedRuleContext(SqlBaseParser.TableProviderContext,0) + + def query(self): + return self.getTypedRuleContext(SqlBaseParser.QueryContext,0) + + def AS(self): + return self.getToken(SqlBaseParser.AS, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterCreateTable" ): + listener.enterCreateTable(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitCreateTable" ): + listener.exitCreateTable(self) + + + class DmlStatementContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def dmlStatementNoWith(self): + return self.getTypedRuleContext(SqlBaseParser.DmlStatementNoWithContext,0) + + def ctes(self): + return self.getTypedRuleContext(SqlBaseParser.CtesContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterDmlStatement" ): + listener.enterDmlStatement(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitDmlStatement" ): + listener.exitDmlStatement(self) + + + class CreateTableLikeContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.target = None # TableIdentifierContext + self.source = None # TableIdentifierContext + self.tableProps = None # PropertyListContext + self.copyFrom(ctx) + + def CREATE(self): + return self.getToken(SqlBaseParser.CREATE, 0) + def TABLE(self): + return self.getToken(SqlBaseParser.TABLE, 0) + def LIKE(self): + return self.getToken(SqlBaseParser.LIKE, 0) + def tableIdentifier(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.TableIdentifierContext) + else: + return self.getTypedRuleContext(SqlBaseParser.TableIdentifierContext,i) + + def IF(self): + return self.getToken(SqlBaseParser.IF, 0) + def NOT(self): + return self.getToken(SqlBaseParser.NOT, 0) + def EXISTS(self): + return self.getToken(SqlBaseParser.EXISTS, 0) + def tableProvider(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.TableProviderContext) + else: + return self.getTypedRuleContext(SqlBaseParser.TableProviderContext,i) + + def rowFormat(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.RowFormatContext) + else: + return self.getTypedRuleContext(SqlBaseParser.RowFormatContext,i) + + def createFileFormat(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.CreateFileFormatContext) + else: + return self.getTypedRuleContext(SqlBaseParser.CreateFileFormatContext,i) + + def locationSpec(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.LocationSpecContext) + else: + return self.getTypedRuleContext(SqlBaseParser.LocationSpecContext,i) + + def TBLPROPERTIES(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.TBLPROPERTIES) + else: + return self.getToken(SqlBaseParser.TBLPROPERTIES, i) + def propertyList(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.PropertyListContext) + else: + return self.getTypedRuleContext(SqlBaseParser.PropertyListContext,i) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterCreateTableLike" ): + listener.enterCreateTableLike(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitCreateTableLike" ): + listener.exitCreateTableLike(self) + + + class UncacheTableContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def UNCACHE(self): + return self.getToken(SqlBaseParser.UNCACHE, 0) + def TABLE(self): + return self.getToken(SqlBaseParser.TABLE, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def IF(self): + return self.getToken(SqlBaseParser.IF, 0) + def EXISTS(self): + return self.getToken(SqlBaseParser.EXISTS, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterUncacheTable" ): + listener.enterUncacheTable(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitUncacheTable" ): + listener.exitUncacheTable(self) + + + class DropFunctionContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def DROP(self): + return self.getToken(SqlBaseParser.DROP, 0) + def FUNCTION(self): + return self.getToken(SqlBaseParser.FUNCTION, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def TEMPORARY(self): + return self.getToken(SqlBaseParser.TEMPORARY, 0) + def IF(self): + return self.getToken(SqlBaseParser.IF, 0) + def EXISTS(self): + return self.getToken(SqlBaseParser.EXISTS, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterDropFunction" ): + listener.enterDropFunction(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitDropFunction" ): + listener.exitDropFunction(self) + + + class DescribeRelationContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.option = None # Token + self.copyFrom(ctx) + + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def DESC(self): + return self.getToken(SqlBaseParser.DESC, 0) + def DESCRIBE(self): + return self.getToken(SqlBaseParser.DESCRIBE, 0) + def TABLE(self): + return self.getToken(SqlBaseParser.TABLE, 0) + def partitionSpec(self): + return self.getTypedRuleContext(SqlBaseParser.PartitionSpecContext,0) + + def describeColName(self): + return self.getTypedRuleContext(SqlBaseParser.DescribeColNameContext,0) + + def EXTENDED(self): + return self.getToken(SqlBaseParser.EXTENDED, 0) + def FORMATTED(self): + return self.getToken(SqlBaseParser.FORMATTED, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterDescribeRelation" ): + listener.enterDescribeRelation(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitDescribeRelation" ): + listener.exitDescribeRelation(self) + + + class LoadDataContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.path = None # StringLitContext + self.copyFrom(ctx) + + def LOAD(self): + return self.getToken(SqlBaseParser.LOAD, 0) + def DATA(self): + return self.getToken(SqlBaseParser.DATA, 0) + def INPATH(self): + return self.getToken(SqlBaseParser.INPATH, 0) + def INTO(self): + return self.getToken(SqlBaseParser.INTO, 0) + def TABLE(self): + return self.getToken(SqlBaseParser.TABLE, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def stringLit(self): + return self.getTypedRuleContext(SqlBaseParser.StringLitContext,0) + + def LOCAL(self): + return self.getToken(SqlBaseParser.LOCAL, 0) + def OVERWRITE(self): + return self.getToken(SqlBaseParser.OVERWRITE, 0) + def partitionSpec(self): + return self.getTypedRuleContext(SqlBaseParser.PartitionSpecContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterLoadData" ): + listener.enterLoadData(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitLoadData" ): + listener.exitLoadData(self) + + + class ShowPartitionsContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def SHOW(self): + return self.getToken(SqlBaseParser.SHOW, 0) + def PARTITIONS(self): + return self.getToken(SqlBaseParser.PARTITIONS, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def partitionSpec(self): + return self.getTypedRuleContext(SqlBaseParser.PartitionSpecContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterShowPartitions" ): + listener.enterShowPartitions(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitShowPartitions" ): + listener.exitShowPartitions(self) + + + class DescribeFunctionContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def FUNCTION(self): + return self.getToken(SqlBaseParser.FUNCTION, 0) + def describeFuncName(self): + return self.getTypedRuleContext(SqlBaseParser.DescribeFuncNameContext,0) + + def DESC(self): + return self.getToken(SqlBaseParser.DESC, 0) + def DESCRIBE(self): + return self.getToken(SqlBaseParser.DESCRIBE, 0) + def EXTENDED(self): + return self.getToken(SqlBaseParser.EXTENDED, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterDescribeFunction" ): + listener.enterDescribeFunction(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitDescribeFunction" ): + listener.exitDescribeFunction(self) + + + class RenameTableColumnContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.table = None # MultipartIdentifierContext + self.from_ = None # MultipartIdentifierContext + self.to = None # ErrorCapturingIdentifierContext + self.copyFrom(ctx) + + def ALTER(self): + return self.getToken(SqlBaseParser.ALTER, 0) + def TABLE(self): + return self.getToken(SqlBaseParser.TABLE, 0) + def RENAME(self): + return self.getToken(SqlBaseParser.RENAME, 0) + def COLUMN(self): + return self.getToken(SqlBaseParser.COLUMN, 0) + def TO(self): + return self.getToken(SqlBaseParser.TO, 0) + def multipartIdentifier(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.MultipartIdentifierContext) + else: + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,i) + + def errorCapturingIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.ErrorCapturingIdentifierContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterRenameTableColumn" ): + listener.enterRenameTableColumn(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitRenameTableColumn" ): + listener.exitRenameTableColumn(self) + + + class StatementDefaultContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def query(self): + return self.getTypedRuleContext(SqlBaseParser.QueryContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterStatementDefault" ): + listener.enterStatementDefault(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitStatementDefault" ): + listener.exitStatementDefault(self) + + + class HiveChangeColumnContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.table = None # MultipartIdentifierContext + self.colName = None # MultipartIdentifierContext + self.copyFrom(ctx) + + def ALTER(self): + return self.getToken(SqlBaseParser.ALTER, 0) + def TABLE(self): + return self.getToken(SqlBaseParser.TABLE, 0) + def CHANGE(self): + return self.getToken(SqlBaseParser.CHANGE, 0) + def colType(self): + return self.getTypedRuleContext(SqlBaseParser.ColTypeContext,0) + + def multipartIdentifier(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.MultipartIdentifierContext) + else: + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,i) + + def partitionSpec(self): + return self.getTypedRuleContext(SqlBaseParser.PartitionSpecContext,0) + + def COLUMN(self): + return self.getToken(SqlBaseParser.COLUMN, 0) + def colPosition(self): + return self.getTypedRuleContext(SqlBaseParser.ColPositionContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterHiveChangeColumn" ): + listener.enterHiveChangeColumn(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitHiveChangeColumn" ): + listener.exitHiveChangeColumn(self) + + + class SetTimeZoneContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def SET(self): + return self.getToken(SqlBaseParser.SET, 0) + def TIME(self): + return self.getToken(SqlBaseParser.TIME, 0) + def ZONE(self): + return self.getToken(SqlBaseParser.ZONE, 0) + def interval(self): + return self.getTypedRuleContext(SqlBaseParser.IntervalContext,0) + + def timezone(self): + return self.getTypedRuleContext(SqlBaseParser.TimezoneContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSetTimeZone" ): + listener.enterSetTimeZone(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSetTimeZone" ): + listener.exitSetTimeZone(self) + + + class DescribeQueryContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def query(self): + return self.getTypedRuleContext(SqlBaseParser.QueryContext,0) + + def DESC(self): + return self.getToken(SqlBaseParser.DESC, 0) + def DESCRIBE(self): + return self.getToken(SqlBaseParser.DESCRIBE, 0) + def QUERY(self): + return self.getToken(SqlBaseParser.QUERY, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterDescribeQuery" ): + listener.enterDescribeQuery(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitDescribeQuery" ): + listener.exitDescribeQuery(self) + + + class TruncateTableContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def TRUNCATE(self): + return self.getToken(SqlBaseParser.TRUNCATE, 0) + def TABLE(self): + return self.getToken(SqlBaseParser.TABLE, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def partitionSpec(self): + return self.getTypedRuleContext(SqlBaseParser.PartitionSpecContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterTruncateTable" ): + listener.enterTruncateTable(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitTruncateTable" ): + listener.exitTruncateTable(self) + + + class SetTableSerDeContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def ALTER(self): + return self.getToken(SqlBaseParser.ALTER, 0) + def TABLE(self): + return self.getToken(SqlBaseParser.TABLE, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def SET(self): + return self.getToken(SqlBaseParser.SET, 0) + def SERDE(self): + return self.getToken(SqlBaseParser.SERDE, 0) + def stringLit(self): + return self.getTypedRuleContext(SqlBaseParser.StringLitContext,0) + + def partitionSpec(self): + return self.getTypedRuleContext(SqlBaseParser.PartitionSpecContext,0) + + def WITH(self): + return self.getToken(SqlBaseParser.WITH, 0) + def SERDEPROPERTIES(self): + return self.getToken(SqlBaseParser.SERDEPROPERTIES, 0) + def propertyList(self): + return self.getTypedRuleContext(SqlBaseParser.PropertyListContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSetTableSerDe" ): + listener.enterSetTableSerDe(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSetTableSerDe" ): + listener.exitSetTableSerDe(self) + + + class CreateViewContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def CREATE(self): + return self.getToken(SqlBaseParser.CREATE, 0) + def VIEW(self): + return self.getToken(SqlBaseParser.VIEW, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def AS(self): + return self.getToken(SqlBaseParser.AS, 0) + def query(self): + return self.getTypedRuleContext(SqlBaseParser.QueryContext,0) + + def OR(self): + return self.getToken(SqlBaseParser.OR, 0) + def REPLACE(self): + return self.getToken(SqlBaseParser.REPLACE, 0) + def TEMPORARY(self): + return self.getToken(SqlBaseParser.TEMPORARY, 0) + def IF(self): + return self.getToken(SqlBaseParser.IF, 0) + def NOT(self): + return self.getToken(SqlBaseParser.NOT, 0) + def EXISTS(self): + return self.getToken(SqlBaseParser.EXISTS, 0) + def identifierCommentList(self): + return self.getTypedRuleContext(SqlBaseParser.IdentifierCommentListContext,0) + + def commentSpec(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.CommentSpecContext) + else: + return self.getTypedRuleContext(SqlBaseParser.CommentSpecContext,i) + + def PARTITIONED(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.PARTITIONED) + else: + return self.getToken(SqlBaseParser.PARTITIONED, i) + def ON(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.ON) + else: + return self.getToken(SqlBaseParser.ON, i) + def identifierList(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.IdentifierListContext) + else: + return self.getTypedRuleContext(SqlBaseParser.IdentifierListContext,i) + + def TBLPROPERTIES(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.TBLPROPERTIES) + else: + return self.getToken(SqlBaseParser.TBLPROPERTIES, i) + def propertyList(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.PropertyListContext) + else: + return self.getTypedRuleContext(SqlBaseParser.PropertyListContext,i) + + def GLOBAL(self): + return self.getToken(SqlBaseParser.GLOBAL, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterCreateView" ): + listener.enterCreateView(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitCreateView" ): + listener.exitCreateView(self) + + + class DropTablePartitionsContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def ALTER(self): + return self.getToken(SqlBaseParser.ALTER, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def DROP(self): + return self.getToken(SqlBaseParser.DROP, 0) + def partitionSpec(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.PartitionSpecContext) + else: + return self.getTypedRuleContext(SqlBaseParser.PartitionSpecContext,i) + + def TABLE(self): + return self.getToken(SqlBaseParser.TABLE, 0) + def VIEW(self): + return self.getToken(SqlBaseParser.VIEW, 0) + def IF(self): + return self.getToken(SqlBaseParser.IF, 0) + def EXISTS(self): + return self.getToken(SqlBaseParser.EXISTS, 0) + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + def PURGE(self): + return self.getToken(SqlBaseParser.PURGE, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterDropTablePartitions" ): + listener.enterDropTablePartitions(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitDropTablePartitions" ): + listener.exitDropTablePartitions(self) + + + class SetConfigurationContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def SET(self): + return self.getToken(SqlBaseParser.SET, 0) + def configKey(self): + return self.getTypedRuleContext(SqlBaseParser.ConfigKeyContext,0) + + def EQ(self): + return self.getToken(SqlBaseParser.EQ, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSetConfiguration" ): + listener.enterSetConfiguration(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSetConfiguration" ): + listener.exitSetConfiguration(self) + + + class DropTableContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def DROP(self): + return self.getToken(SqlBaseParser.DROP, 0) + def TABLE(self): + return self.getToken(SqlBaseParser.TABLE, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def IF(self): + return self.getToken(SqlBaseParser.IF, 0) + def EXISTS(self): + return self.getToken(SqlBaseParser.EXISTS, 0) + def PURGE(self): + return self.getToken(SqlBaseParser.PURGE, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterDropTable" ): + listener.enterDropTable(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitDropTable" ): + listener.exitDropTable(self) + + + class ShowTableExtendedContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.ns = None # MultipartIdentifierContext + self.pattern = None # StringLitContext + self.copyFrom(ctx) + + def SHOW(self): + return self.getToken(SqlBaseParser.SHOW, 0) + def TABLE(self): + return self.getToken(SqlBaseParser.TABLE, 0) + def EXTENDED(self): + return self.getToken(SqlBaseParser.EXTENDED, 0) + def LIKE(self): + return self.getToken(SqlBaseParser.LIKE, 0) + def stringLit(self): + return self.getTypedRuleContext(SqlBaseParser.StringLitContext,0) + + def partitionSpec(self): + return self.getTypedRuleContext(SqlBaseParser.PartitionSpecContext,0) + + def FROM(self): + return self.getToken(SqlBaseParser.FROM, 0) + def IN(self): + return self.getToken(SqlBaseParser.IN, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterShowTableExtended" ): + listener.enterShowTableExtended(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitShowTableExtended" ): + listener.exitShowTableExtended(self) + + + class DescribeNamespaceContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def namespace(self): + return self.getTypedRuleContext(SqlBaseParser.NamespaceContext,0) + + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def DESC(self): + return self.getToken(SqlBaseParser.DESC, 0) + def DESCRIBE(self): + return self.getToken(SqlBaseParser.DESCRIBE, 0) + def EXTENDED(self): + return self.getToken(SqlBaseParser.EXTENDED, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterDescribeNamespace" ): + listener.enterDescribeNamespace(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitDescribeNamespace" ): + listener.exitDescribeNamespace(self) + + + class AlterTableAlterColumnContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.table = None # MultipartIdentifierContext + self.column = None # MultipartIdentifierContext + self.copyFrom(ctx) + + def ALTER(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.ALTER) + else: + return self.getToken(SqlBaseParser.ALTER, i) + def TABLE(self): + return self.getToken(SqlBaseParser.TABLE, 0) + def multipartIdentifier(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.MultipartIdentifierContext) + else: + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,i) + + def CHANGE(self): + return self.getToken(SqlBaseParser.CHANGE, 0) + def COLUMN(self): + return self.getToken(SqlBaseParser.COLUMN, 0) + def alterColumnAction(self): + return self.getTypedRuleContext(SqlBaseParser.AlterColumnActionContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterAlterTableAlterColumn" ): + listener.enterAlterTableAlterColumn(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitAlterTableAlterColumn" ): + listener.exitAlterTableAlterColumn(self) + + + class RefreshFunctionContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def REFRESH(self): + return self.getToken(SqlBaseParser.REFRESH, 0) + def FUNCTION(self): + return self.getToken(SqlBaseParser.FUNCTION, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterRefreshFunction" ): + listener.enterRefreshFunction(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitRefreshFunction" ): + listener.exitRefreshFunction(self) + + + class CommentTableContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def COMMENT(self): + return self.getToken(SqlBaseParser.COMMENT, 0) + def ON(self): + return self.getToken(SqlBaseParser.ON, 0) + def TABLE(self): + return self.getToken(SqlBaseParser.TABLE, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def IS(self): + return self.getToken(SqlBaseParser.IS, 0) + def comment(self): + return self.getTypedRuleContext(SqlBaseParser.CommentContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterCommentTable" ): + listener.enterCommentTable(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitCommentTable" ): + listener.exitCommentTable(self) + + + class CreateIndexContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.indexType = None # IdentifierContext + self.columns = None # MultipartIdentifierPropertyListContext + self.options = None # PropertyListContext + self.copyFrom(ctx) + + def CREATE(self): + return self.getToken(SqlBaseParser.CREATE, 0) + def INDEX(self): + return self.getToken(SqlBaseParser.INDEX, 0) + def identifier(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.IdentifierContext) + else: + return self.getTypedRuleContext(SqlBaseParser.IdentifierContext,i) + + def ON(self): + return self.getToken(SqlBaseParser.ON, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + def multipartIdentifierPropertyList(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierPropertyListContext,0) + + def IF(self): + return self.getToken(SqlBaseParser.IF, 0) + def NOT(self): + return self.getToken(SqlBaseParser.NOT, 0) + def EXISTS(self): + return self.getToken(SqlBaseParser.EXISTS, 0) + def TABLE(self): + return self.getToken(SqlBaseParser.TABLE, 0) + def USING(self): + return self.getToken(SqlBaseParser.USING, 0) + def OPTIONS(self): + return self.getToken(SqlBaseParser.OPTIONS, 0) + def propertyList(self): + return self.getTypedRuleContext(SqlBaseParser.PropertyListContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterCreateIndex" ): + listener.enterCreateIndex(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitCreateIndex" ): + listener.exitCreateIndex(self) + + + class UseNamespaceContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def USE(self): + return self.getToken(SqlBaseParser.USE, 0) + def namespace(self): + return self.getTypedRuleContext(SqlBaseParser.NamespaceContext,0) + + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterUseNamespace" ): + listener.enterUseNamespace(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitUseNamespace" ): + listener.exitUseNamespace(self) + + + class CreateNamespaceContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def CREATE(self): + return self.getToken(SqlBaseParser.CREATE, 0) + def namespace(self): + return self.getTypedRuleContext(SqlBaseParser.NamespaceContext,0) + + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def IF(self): + return self.getToken(SqlBaseParser.IF, 0) + def NOT(self): + return self.getToken(SqlBaseParser.NOT, 0) + def EXISTS(self): + return self.getToken(SqlBaseParser.EXISTS, 0) + def commentSpec(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.CommentSpecContext) + else: + return self.getTypedRuleContext(SqlBaseParser.CommentSpecContext,i) + + def locationSpec(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.LocationSpecContext) + else: + return self.getTypedRuleContext(SqlBaseParser.LocationSpecContext,i) + + def WITH(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.WITH) + else: + return self.getToken(SqlBaseParser.WITH, i) + def propertyList(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.PropertyListContext) + else: + return self.getTypedRuleContext(SqlBaseParser.PropertyListContext,i) + + def DBPROPERTIES(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.DBPROPERTIES) + else: + return self.getToken(SqlBaseParser.DBPROPERTIES, i) + def PROPERTIES(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.PROPERTIES) + else: + return self.getToken(SqlBaseParser.PROPERTIES, i) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterCreateNamespace" ): + listener.enterCreateNamespace(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitCreateNamespace" ): + listener.exitCreateNamespace(self) + + + class ShowTblPropertiesContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.table = None # MultipartIdentifierContext + self.key = None # PropertyKeyContext + self.copyFrom(ctx) + + def SHOW(self): + return self.getToken(SqlBaseParser.SHOW, 0) + def TBLPROPERTIES(self): + return self.getToken(SqlBaseParser.TBLPROPERTIES, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + def propertyKey(self): + return self.getTypedRuleContext(SqlBaseParser.PropertyKeyContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterShowTblProperties" ): + listener.enterShowTblProperties(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitShowTblProperties" ): + listener.exitShowTblProperties(self) + + + class UnsetTablePropertiesContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def ALTER(self): + return self.getToken(SqlBaseParser.ALTER, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def UNSET(self): + return self.getToken(SqlBaseParser.UNSET, 0) + def TBLPROPERTIES(self): + return self.getToken(SqlBaseParser.TBLPROPERTIES, 0) + def propertyList(self): + return self.getTypedRuleContext(SqlBaseParser.PropertyListContext,0) + + def TABLE(self): + return self.getToken(SqlBaseParser.TABLE, 0) + def VIEW(self): + return self.getToken(SqlBaseParser.VIEW, 0) + def IF(self): + return self.getToken(SqlBaseParser.IF, 0) + def EXISTS(self): + return self.getToken(SqlBaseParser.EXISTS, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterUnsetTableProperties" ): + listener.enterUnsetTableProperties(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitUnsetTableProperties" ): + listener.exitUnsetTableProperties(self) + + + class SetTableLocationContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def ALTER(self): + return self.getToken(SqlBaseParser.ALTER, 0) + def TABLE(self): + return self.getToken(SqlBaseParser.TABLE, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def SET(self): + return self.getToken(SqlBaseParser.SET, 0) + def locationSpec(self): + return self.getTypedRuleContext(SqlBaseParser.LocationSpecContext,0) + + def partitionSpec(self): + return self.getTypedRuleContext(SqlBaseParser.PartitionSpecContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSetTableLocation" ): + listener.enterSetTableLocation(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSetTableLocation" ): + listener.exitSetTableLocation(self) + + + class DropTableColumnsContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.columns = None # MultipartIdentifierListContext + self.copyFrom(ctx) + + def ALTER(self): + return self.getToken(SqlBaseParser.ALTER, 0) + def TABLE(self): + return self.getToken(SqlBaseParser.TABLE, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def DROP(self): + return self.getToken(SqlBaseParser.DROP, 0) + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + def COLUMN(self): + return self.getToken(SqlBaseParser.COLUMN, 0) + def COLUMNS(self): + return self.getToken(SqlBaseParser.COLUMNS, 0) + def multipartIdentifierList(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierListContext,0) + + def IF(self): + return self.getToken(SqlBaseParser.IF, 0) + def EXISTS(self): + return self.getToken(SqlBaseParser.EXISTS, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterDropTableColumns" ): + listener.enterDropTableColumns(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitDropTableColumns" ): + listener.exitDropTableColumns(self) + + + class ShowViewsContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.pattern = None # StringLitContext + self.copyFrom(ctx) + + def SHOW(self): + return self.getToken(SqlBaseParser.SHOW, 0) + def VIEWS(self): + return self.getToken(SqlBaseParser.VIEWS, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def FROM(self): + return self.getToken(SqlBaseParser.FROM, 0) + def IN(self): + return self.getToken(SqlBaseParser.IN, 0) + def stringLit(self): + return self.getTypedRuleContext(SqlBaseParser.StringLitContext,0) + + def LIKE(self): + return self.getToken(SqlBaseParser.LIKE, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterShowViews" ): + listener.enterShowViews(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitShowViews" ): + listener.exitShowViews(self) + + + class ShowFunctionsContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.ns = None # MultipartIdentifierContext + self.legacy = None # MultipartIdentifierContext + self.pattern = None # StringLitContext + self.copyFrom(ctx) + + def SHOW(self): + return self.getToken(SqlBaseParser.SHOW, 0) + def FUNCTIONS(self): + return self.getToken(SqlBaseParser.FUNCTIONS, 0) + def identifier(self): + return self.getTypedRuleContext(SqlBaseParser.IdentifierContext,0) + + def FROM(self): + return self.getToken(SqlBaseParser.FROM, 0) + def IN(self): + return self.getToken(SqlBaseParser.IN, 0) + def multipartIdentifier(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.MultipartIdentifierContext) + else: + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,i) + + def LIKE(self): + return self.getToken(SqlBaseParser.LIKE, 0) + def stringLit(self): + return self.getTypedRuleContext(SqlBaseParser.StringLitContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterShowFunctions" ): + listener.enterShowFunctions(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitShowFunctions" ): + listener.exitShowFunctions(self) + + + class CacheTableContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.options = None # PropertyListContext + self.copyFrom(ctx) + + def CACHE(self): + return self.getToken(SqlBaseParser.CACHE, 0) + def TABLE(self): + return self.getToken(SqlBaseParser.TABLE, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def LAZY(self): + return self.getToken(SqlBaseParser.LAZY, 0) + def OPTIONS(self): + return self.getToken(SqlBaseParser.OPTIONS, 0) + def query(self): + return self.getTypedRuleContext(SqlBaseParser.QueryContext,0) + + def propertyList(self): + return self.getTypedRuleContext(SqlBaseParser.PropertyListContext,0) + + def AS(self): + return self.getToken(SqlBaseParser.AS, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterCacheTable" ): + listener.enterCacheTable(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitCacheTable" ): + listener.exitCacheTable(self) + + + class AddTableColumnsContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.columns = None # QualifiedColTypeWithPositionListContext + self.copyFrom(ctx) + + def ALTER(self): + return self.getToken(SqlBaseParser.ALTER, 0) + def TABLE(self): + return self.getToken(SqlBaseParser.TABLE, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def ADD(self): + return self.getToken(SqlBaseParser.ADD, 0) + def COLUMN(self): + return self.getToken(SqlBaseParser.COLUMN, 0) + def COLUMNS(self): + return self.getToken(SqlBaseParser.COLUMNS, 0) + def qualifiedColTypeWithPositionList(self): + return self.getTypedRuleContext(SqlBaseParser.QualifiedColTypeWithPositionListContext,0) + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterAddTableColumns" ): + listener.enterAddTableColumns(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitAddTableColumns" ): + listener.exitAddTableColumns(self) + + + class SetTablePropertiesContext(StatementContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StatementContext + super().__init__(parser) + self.copyFrom(ctx) + + def ALTER(self): + return self.getToken(SqlBaseParser.ALTER, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def SET(self): + return self.getToken(SqlBaseParser.SET, 0) + def TBLPROPERTIES(self): + return self.getToken(SqlBaseParser.TBLPROPERTIES, 0) + def propertyList(self): + return self.getTypedRuleContext(SqlBaseParser.PropertyListContext,0) + + def TABLE(self): + return self.getToken(SqlBaseParser.TABLE, 0) + def VIEW(self): + return self.getToken(SqlBaseParser.VIEW, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSetTableProperties" ): + listener.enterSetTableProperties(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSetTableProperties" ): + listener.exitSetTableProperties(self) + + + + def statement(self): + + localctx = SqlBaseParser.StatementContext(self, self._ctx, self.state) + self.enterRule(localctx, 14, self.RULE_statement) + self._la = 0 # Token type + try: + self.state = 1180 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,121,self._ctx) + if la_ == 1: + localctx = SqlBaseParser.StatementDefaultContext(self, localctx) + self.enterOuterAlt(localctx, 1) + self.state = 377 + self.query() + pass + + elif la_ == 2: + localctx = SqlBaseParser.DmlStatementContext(self, localctx) + self.enterOuterAlt(localctx, 2) + self.state = 379 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==303: + self.state = 378 + self.ctes() + + + self.state = 381 + self.dmlStatementNoWith() + pass + + elif la_ == 3: + localctx = SqlBaseParser.UseContext(self, localctx) + self.enterOuterAlt(localctx, 3) + self.state = 382 + self.match(SqlBaseParser.USE) + self.state = 383 + self.multipartIdentifier() + pass + + elif la_ == 4: + localctx = SqlBaseParser.UseNamespaceContext(self, localctx) + self.enterOuterAlt(localctx, 4) + self.state = 384 + self.match(SqlBaseParser.USE) + self.state = 385 + self.namespace() + self.state = 386 + self.multipartIdentifier() + pass + + elif la_ == 5: + localctx = SqlBaseParser.SetCatalogContext(self, localctx) + self.enterOuterAlt(localctx, 5) + self.state = 388 + self.match(SqlBaseParser.SET) + self.state = 389 + self.match(SqlBaseParser.CATALOG) + self.state = 392 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,2,self._ctx) + if la_ == 1: + self.state = 390 + self.identifier() + pass + + elif la_ == 2: + self.state = 391 + self.stringLit() + pass + + + pass + + elif la_ == 6: + localctx = SqlBaseParser.CreateNamespaceContext(self, localctx) + self.enterOuterAlt(localctx, 6) + self.state = 394 + self.match(SqlBaseParser.CREATE) + self.state = 395 + self.namespace() + self.state = 399 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,3,self._ctx) + if la_ == 1: + self.state = 396 + self.match(SqlBaseParser.IF) + self.state = 397 + self.match(SqlBaseParser.NOT) + self.state = 398 + self.match(SqlBaseParser.EXISTS) + + + self.state = 401 + self.multipartIdentifier() + self.state = 409 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==45 or _la==149 or _la==303: + self.state = 407 + self._errHandler.sync(self) + token = self._input.LA(1) + if token in [45]: + self.state = 402 + self.commentSpec() + pass + elif token in [149]: + self.state = 403 + self.locationSpec() + pass + elif token in [303]: + self.state = 404 + self.match(SqlBaseParser.WITH) + self.state = 405 + _la = self._input.LA(1) + if not(_la==69 or _la==202): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 406 + self.propertyList() + pass + else: + raise NoViableAltException(self) + + self.state = 411 + self._errHandler.sync(self) + _la = self._input.LA(1) + + pass + + elif la_ == 7: + localctx = SqlBaseParser.SetNamespacePropertiesContext(self, localctx) + self.enterOuterAlt(localctx, 7) + self.state = 412 + self.match(SqlBaseParser.ALTER) + self.state = 413 + self.namespace() + self.state = 414 + self.multipartIdentifier() + self.state = 415 + self.match(SqlBaseParser.SET) + self.state = 416 + _la = self._input.LA(1) + if not(_la==69 or _la==202): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 417 + self.propertyList() + pass + + elif la_ == 8: + localctx = SqlBaseParser.SetNamespaceLocationContext(self, localctx) + self.enterOuterAlt(localctx, 8) + self.state = 419 + self.match(SqlBaseParser.ALTER) + self.state = 420 + self.namespace() + self.state = 421 + self.multipartIdentifier() + self.state = 422 + self.match(SqlBaseParser.SET) + self.state = 423 + self.locationSpec() + pass + + elif la_ == 9: + localctx = SqlBaseParser.DropNamespaceContext(self, localctx) + self.enterOuterAlt(localctx, 9) + self.state = 425 + self.match(SqlBaseParser.DROP) + self.state = 426 + self.namespace() + self.state = 429 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,6,self._ctx) + if la_ == 1: + self.state = 427 + self.match(SqlBaseParser.IF) + self.state = 428 + self.match(SqlBaseParser.EXISTS) + + + self.state = 431 + self.multipartIdentifier() + self.state = 433 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==30 or _la==219: + self.state = 432 + _la = self._input.LA(1) + if not(_la==30 or _la==219): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + + + pass + + elif la_ == 10: + localctx = SqlBaseParser.ShowNamespacesContext(self, localctx) + self.enterOuterAlt(localctx, 10) + self.state = 435 + self.match(SqlBaseParser.SHOW) + self.state = 436 + self.namespaces() + self.state = 439 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==107 or _la==122: + self.state = 437 + _la = self._input.LA(1) + if not(_la==107 or _la==122): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 438 + self.multipartIdentifier() + + + self.state = 445 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==142 or _la==330 or _la==331: + self.state = 442 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==142: + self.state = 441 + self.match(SqlBaseParser.LIKE) + + + self.state = 444 + localctx.pattern = self.stringLit() + + + pass + + elif la_ == 11: + localctx = SqlBaseParser.CreateTableContext(self, localctx) + self.enterOuterAlt(localctx, 11) + self.state = 447 + self.createTableHeader() + self.state = 452 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,11,self._ctx) + if la_ == 1: + self.state = 448 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 449 + self.createOrReplaceTableColTypeList() + self.state = 450 + self.match(SqlBaseParser.RIGHT_PAREN) + + + self.state = 455 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==293: + self.state = 454 + self.tableProvider() + + + self.state = 457 + self.createTableClauses() + self.state = 462 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==2 or _la==20 or _la==107 or _la==154 or ((((_la - 210)) & ~0x3f) == 0 and ((1 << (_la - 210)) & 281474985099265) != 0) or _la==294 or _la==303: + self.state = 459 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==20: + self.state = 458 + self.match(SqlBaseParser.AS) + + + self.state = 461 + self.query() + + + pass + + elif la_ == 12: + localctx = SqlBaseParser.CreateTableLikeContext(self, localctx) + self.enterOuterAlt(localctx, 12) + self.state = 464 + self.match(SqlBaseParser.CREATE) + self.state = 465 + self.match(SqlBaseParser.TABLE) + self.state = 469 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,15,self._ctx) + if la_ == 1: + self.state = 466 + self.match(SqlBaseParser.IF) + self.state = 467 + self.match(SqlBaseParser.NOT) + self.state = 468 + self.match(SqlBaseParser.EXISTS) + + + self.state = 471 + localctx.target = self.tableIdentifier() + self.state = 472 + self.match(SqlBaseParser.LIKE) + self.state = 473 + localctx.source = self.tableIdentifier() + self.state = 482 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==149 or ((((_la - 227)) & ~0x3f) == 0 and ((1 << (_la - 227)) & 34368126977) != 0) or _la==293: + self.state = 480 + self._errHandler.sync(self) + token = self._input.LA(1) + if token in [293]: + self.state = 474 + self.tableProvider() + pass + elif token in [227]: + self.state = 475 + self.rowFormat() + pass + elif token in [250]: + self.state = 476 + self.createFileFormat() + pass + elif token in [149]: + self.state = 477 + self.locationSpec() + pass + elif token in [262]: + self.state = 478 + self.match(SqlBaseParser.TBLPROPERTIES) + self.state = 479 + localctx.tableProps = self.propertyList() + pass + else: + raise NoViableAltException(self) + + self.state = 484 + self._errHandler.sync(self) + _la = self._input.LA(1) + + pass + + elif la_ == 13: + localctx = SqlBaseParser.ReplaceTableContext(self, localctx) + self.enterOuterAlt(localctx, 13) + self.state = 485 + self.replaceTableHeader() + self.state = 490 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,18,self._ctx) + if la_ == 1: + self.state = 486 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 487 + self.createOrReplaceTableColTypeList() + self.state = 488 + self.match(SqlBaseParser.RIGHT_PAREN) + + + self.state = 493 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==293: + self.state = 492 + self.tableProvider() + + + self.state = 495 + self.createTableClauses() + self.state = 500 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==2 or _la==20 or _la==107 or _la==154 or ((((_la - 210)) & ~0x3f) == 0 and ((1 << (_la - 210)) & 281474985099265) != 0) or _la==294 or _la==303: + self.state = 497 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==20: + self.state = 496 + self.match(SqlBaseParser.AS) + + + self.state = 499 + self.query() + + + pass + + elif la_ == 14: + localctx = SqlBaseParser.AnalyzeContext(self, localctx) + self.enterOuterAlt(localctx, 14) + self.state = 502 + self.match(SqlBaseParser.ANALYZE) + self.state = 503 + self.match(SqlBaseParser.TABLE) + self.state = 504 + self.multipartIdentifier() + self.state = 506 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==190: + self.state = 505 + self.partitionSpec() + + + self.state = 508 + self.match(SqlBaseParser.COMPUTE) + self.state = 509 + self.match(SqlBaseParser.STATISTICS) + self.state = 517 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,23,self._ctx) + if la_ == 1: + self.state = 510 + self.identifier() + + elif la_ == 2: + self.state = 511 + self.match(SqlBaseParser.FOR) + self.state = 512 + self.match(SqlBaseParser.COLUMNS) + self.state = 513 + self.identifierSeq() + + elif la_ == 3: + self.state = 514 + self.match(SqlBaseParser.FOR) + self.state = 515 + self.match(SqlBaseParser.ALL) + self.state = 516 + self.match(SqlBaseParser.COLUMNS) + + + pass + + elif la_ == 15: + localctx = SqlBaseParser.AnalyzeTablesContext(self, localctx) + self.enterOuterAlt(localctx, 15) + self.state = 519 + self.match(SqlBaseParser.ANALYZE) + self.state = 520 + self.match(SqlBaseParser.TABLES) + self.state = 523 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==107 or _la==122: + self.state = 521 + _la = self._input.LA(1) + if not(_la==107 or _la==122): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 522 + self.multipartIdentifier() + + + self.state = 525 + self.match(SqlBaseParser.COMPUTE) + self.state = 526 + self.match(SqlBaseParser.STATISTICS) + self.state = 528 + self._errHandler.sync(self) + _la = self._input.LA(1) + if (((_la) & ~0x3f) == 0 and ((1 << _la) & -256) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & -1) != 0) or ((((_la - 128)) & ~0x3f) == 0 and ((1 << (_la - 128)) & -1) != 0) or ((((_la - 192)) & ~0x3f) == 0 and ((1 << (_la - 192)) & -1) != 0) or ((((_la - 256)) & ~0x3f) == 0 and ((1 << (_la - 256)) & 4503599627370495) != 0) or ((((_la - 331)) & ~0x3f) == 0 and ((1 << (_la - 331)) & 3073) != 0): + self.state = 527 + self.identifier() + + + pass + + elif la_ == 16: + localctx = SqlBaseParser.AddTableColumnsContext(self, localctx) + self.enterOuterAlt(localctx, 16) + self.state = 530 + self.match(SqlBaseParser.ALTER) + self.state = 531 + self.match(SqlBaseParser.TABLE) + self.state = 532 + self.multipartIdentifier() + self.state = 533 + self.match(SqlBaseParser.ADD) + self.state = 534 + _la = self._input.LA(1) + if not(_la==43 or _la==44): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 535 + localctx.columns = self.qualifiedColTypeWithPositionList() + pass + + elif la_ == 17: + localctx = SqlBaseParser.AddTableColumnsContext(self, localctx) + self.enterOuterAlt(localctx, 17) + self.state = 537 + self.match(SqlBaseParser.ALTER) + self.state = 538 + self.match(SqlBaseParser.TABLE) + self.state = 539 + self.multipartIdentifier() + self.state = 540 + self.match(SqlBaseParser.ADD) + self.state = 541 + _la = self._input.LA(1) + if not(_la==43 or _la==44): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 542 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 543 + localctx.columns = self.qualifiedColTypeWithPositionList() + self.state = 544 + self.match(SqlBaseParser.RIGHT_PAREN) + pass + + elif la_ == 18: + localctx = SqlBaseParser.RenameTableColumnContext(self, localctx) + self.enterOuterAlt(localctx, 18) + self.state = 546 + self.match(SqlBaseParser.ALTER) + self.state = 547 + self.match(SqlBaseParser.TABLE) + self.state = 548 + localctx.table = self.multipartIdentifier() + self.state = 549 + self.match(SqlBaseParser.RENAME) + self.state = 550 + self.match(SqlBaseParser.COLUMN) + self.state = 551 + localctx.from_ = self.multipartIdentifier() + self.state = 552 + self.match(SqlBaseParser.TO) + self.state = 553 + localctx.to = self.errorCapturingIdentifier() + pass + + elif la_ == 19: + localctx = SqlBaseParser.DropTableColumnsContext(self, localctx) + self.enterOuterAlt(localctx, 19) + self.state = 555 + self.match(SqlBaseParser.ALTER) + self.state = 556 + self.match(SqlBaseParser.TABLE) + self.state = 557 + self.multipartIdentifier() + self.state = 558 + self.match(SqlBaseParser.DROP) + self.state = 559 + _la = self._input.LA(1) + if not(_la==43 or _la==44): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 562 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==119: + self.state = 560 + self.match(SqlBaseParser.IF) + self.state = 561 + self.match(SqlBaseParser.EXISTS) + + + self.state = 564 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 565 + localctx.columns = self.multipartIdentifierList() + self.state = 566 + self.match(SqlBaseParser.RIGHT_PAREN) + pass + + elif la_ == 20: + localctx = SqlBaseParser.DropTableColumnsContext(self, localctx) + self.enterOuterAlt(localctx, 20) + self.state = 568 + self.match(SqlBaseParser.ALTER) + self.state = 569 + self.match(SqlBaseParser.TABLE) + self.state = 570 + self.multipartIdentifier() + self.state = 571 + self.match(SqlBaseParser.DROP) + self.state = 572 + _la = self._input.LA(1) + if not(_la==43 or _la==44): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 575 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,27,self._ctx) + if la_ == 1: + self.state = 573 + self.match(SqlBaseParser.IF) + self.state = 574 + self.match(SqlBaseParser.EXISTS) + + + self.state = 577 + localctx.columns = self.multipartIdentifierList() + pass + + elif la_ == 21: + localctx = SqlBaseParser.RenameTableContext(self, localctx) + self.enterOuterAlt(localctx, 21) + self.state = 579 + self.match(SqlBaseParser.ALTER) + self.state = 580 + _la = self._input.LA(1) + if not(_la==258 or _la==296): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 581 + localctx.from_ = self.multipartIdentifier() + self.state = 582 + self.match(SqlBaseParser.RENAME) + self.state = 583 + self.match(SqlBaseParser.TO) + self.state = 584 + localctx.to = self.multipartIdentifier() + pass + + elif la_ == 22: + localctx = SqlBaseParser.SetTablePropertiesContext(self, localctx) + self.enterOuterAlt(localctx, 22) + self.state = 586 + self.match(SqlBaseParser.ALTER) + self.state = 587 + _la = self._input.LA(1) + if not(_la==258 or _la==296): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 588 + self.multipartIdentifier() + self.state = 589 + self.match(SqlBaseParser.SET) + self.state = 590 + self.match(SqlBaseParser.TBLPROPERTIES) + self.state = 591 + self.propertyList() + pass + + elif la_ == 23: + localctx = SqlBaseParser.UnsetTablePropertiesContext(self, localctx) + self.enterOuterAlt(localctx, 23) + self.state = 593 + self.match(SqlBaseParser.ALTER) + self.state = 594 + _la = self._input.LA(1) + if not(_la==258 or _la==296): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 595 + self.multipartIdentifier() + self.state = 596 + self.match(SqlBaseParser.UNSET) + self.state = 597 + self.match(SqlBaseParser.TBLPROPERTIES) + self.state = 600 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==119: + self.state = 598 + self.match(SqlBaseParser.IF) + self.state = 599 + self.match(SqlBaseParser.EXISTS) + + + self.state = 602 + self.propertyList() + pass + + elif la_ == 24: + localctx = SqlBaseParser.AlterTableAlterColumnContext(self, localctx) + self.enterOuterAlt(localctx, 24) + self.state = 604 + self.match(SqlBaseParser.ALTER) + self.state = 605 + self.match(SqlBaseParser.TABLE) + self.state = 606 + localctx.table = self.multipartIdentifier() + self.state = 607 + _la = self._input.LA(1) + if not(_la==11 or _la==35): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 609 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,29,self._ctx) + if la_ == 1: + self.state = 608 + self.match(SqlBaseParser.COLUMN) + + + self.state = 611 + localctx.column = self.multipartIdentifier() + self.state = 613 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==9 or _la==45 or _la==82 or _la==101 or _la==239 or _la==280: + self.state = 612 + self.alterColumnAction() + + + pass + + elif la_ == 25: + localctx = SqlBaseParser.HiveChangeColumnContext(self, localctx) + self.enterOuterAlt(localctx, 25) + self.state = 615 + self.match(SqlBaseParser.ALTER) + self.state = 616 + self.match(SqlBaseParser.TABLE) + self.state = 617 + localctx.table = self.multipartIdentifier() + self.state = 619 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==190: + self.state = 618 + self.partitionSpec() + + + self.state = 621 + self.match(SqlBaseParser.CHANGE) + self.state = 623 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,32,self._ctx) + if la_ == 1: + self.state = 622 + self.match(SqlBaseParser.COLUMN) + + + self.state = 625 + localctx.colName = self.multipartIdentifier() + self.state = 626 + self.colType() + self.state = 628 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==9 or _la==101: + self.state = 627 + self.colPosition() + + + pass + + elif la_ == 26: + localctx = SqlBaseParser.HiveReplaceColumnsContext(self, localctx) + self.enterOuterAlt(localctx, 26) + self.state = 630 + self.match(SqlBaseParser.ALTER) + self.state = 631 + self.match(SqlBaseParser.TABLE) + self.state = 632 + localctx.table = self.multipartIdentifier() + self.state = 634 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==190: + self.state = 633 + self.partitionSpec() + + + self.state = 636 + self.match(SqlBaseParser.REPLACE) + self.state = 637 + self.match(SqlBaseParser.COLUMNS) + self.state = 638 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 639 + localctx.columns = self.qualifiedColTypeWithPositionList() + self.state = 640 + self.match(SqlBaseParser.RIGHT_PAREN) + pass + + elif la_ == 27: + localctx = SqlBaseParser.SetTableSerDeContext(self, localctx) + self.enterOuterAlt(localctx, 27) + self.state = 642 + self.match(SqlBaseParser.ALTER) + self.state = 643 + self.match(SqlBaseParser.TABLE) + self.state = 644 + self.multipartIdentifier() + self.state = 646 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==190: + self.state = 645 + self.partitionSpec() + + + self.state = 648 + self.match(SqlBaseParser.SET) + self.state = 649 + self.match(SqlBaseParser.SERDE) + self.state = 650 + self.stringLit() + self.state = 654 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==303: + self.state = 651 + self.match(SqlBaseParser.WITH) + self.state = 652 + self.match(SqlBaseParser.SERDEPROPERTIES) + self.state = 653 + self.propertyList() + + + pass + + elif la_ == 28: + localctx = SqlBaseParser.SetTableSerDeContext(self, localctx) + self.enterOuterAlt(localctx, 28) + self.state = 656 + self.match(SqlBaseParser.ALTER) + self.state = 657 + self.match(SqlBaseParser.TABLE) + self.state = 658 + self.multipartIdentifier() + self.state = 660 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==190: + self.state = 659 + self.partitionSpec() + + + self.state = 662 + self.match(SqlBaseParser.SET) + self.state = 663 + self.match(SqlBaseParser.SERDEPROPERTIES) + self.state = 664 + self.propertyList() + pass + + elif la_ == 29: + localctx = SqlBaseParser.AddTablePartitionContext(self, localctx) + self.enterOuterAlt(localctx, 29) + self.state = 666 + self.match(SqlBaseParser.ALTER) + self.state = 667 + _la = self._input.LA(1) + if not(_la==258 or _la==296): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 668 + self.multipartIdentifier() + self.state = 669 + self.match(SqlBaseParser.ADD) + self.state = 673 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==119: + self.state = 670 + self.match(SqlBaseParser.IF) + self.state = 671 + self.match(SqlBaseParser.NOT) + self.state = 672 + self.match(SqlBaseParser.EXISTS) + + + self.state = 676 + self._errHandler.sync(self) + _la = self._input.LA(1) + while True: + self.state = 675 + self.partitionSpecLocation() + self.state = 678 + self._errHandler.sync(self) + _la = self._input.LA(1) + if not (_la==190): + break + + pass + + elif la_ == 30: + localctx = SqlBaseParser.RenameTablePartitionContext(self, localctx) + self.enterOuterAlt(localctx, 30) + self.state = 680 + self.match(SqlBaseParser.ALTER) + self.state = 681 + self.match(SqlBaseParser.TABLE) + self.state = 682 + self.multipartIdentifier() + self.state = 683 + localctx.from_ = self.partitionSpec() + self.state = 684 + self.match(SqlBaseParser.RENAME) + self.state = 685 + self.match(SqlBaseParser.TO) + self.state = 686 + localctx.to = self.partitionSpec() + pass + + elif la_ == 31: + localctx = SqlBaseParser.DropTablePartitionsContext(self, localctx) + self.enterOuterAlt(localctx, 31) + self.state = 688 + self.match(SqlBaseParser.ALTER) + self.state = 689 + _la = self._input.LA(1) + if not(_la==258 or _la==296): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 690 + self.multipartIdentifier() + self.state = 691 + self.match(SqlBaseParser.DROP) + self.state = 694 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==119: + self.state = 692 + self.match(SqlBaseParser.IF) + self.state = 693 + self.match(SqlBaseParser.EXISTS) + + + self.state = 696 + self.partitionSpec() + self.state = 701 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 697 + self.match(SqlBaseParser.COMMA) + self.state = 698 + self.partitionSpec() + self.state = 703 + self._errHandler.sync(self) + _la = self._input.LA(1) + + self.state = 705 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==203: + self.state = 704 + self.match(SqlBaseParser.PURGE) + + + pass + + elif la_ == 32: + localctx = SqlBaseParser.SetTableLocationContext(self, localctx) + self.enterOuterAlt(localctx, 32) + self.state = 707 + self.match(SqlBaseParser.ALTER) + self.state = 708 + self.match(SqlBaseParser.TABLE) + self.state = 709 + self.multipartIdentifier() + self.state = 711 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==190: + self.state = 710 + self.partitionSpec() + + + self.state = 713 + self.match(SqlBaseParser.SET) + self.state = 714 + self.locationSpec() + pass + + elif la_ == 33: + localctx = SqlBaseParser.RecoverPartitionsContext(self, localctx) + self.enterOuterAlt(localctx, 33) + self.state = 716 + self.match(SqlBaseParser.ALTER) + self.state = 717 + self.match(SqlBaseParser.TABLE) + self.state = 718 + self.multipartIdentifier() + self.state = 719 + self.match(SqlBaseParser.RECOVER) + self.state = 720 + self.match(SqlBaseParser.PARTITIONS) + pass + + elif la_ == 34: + localctx = SqlBaseParser.DropTableContext(self, localctx) + self.enterOuterAlt(localctx, 34) + self.state = 722 + self.match(SqlBaseParser.DROP) + self.state = 723 + self.match(SqlBaseParser.TABLE) + self.state = 726 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,44,self._ctx) + if la_ == 1: + self.state = 724 + self.match(SqlBaseParser.IF) + self.state = 725 + self.match(SqlBaseParser.EXISTS) + + + self.state = 728 + self.multipartIdentifier() + self.state = 730 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==203: + self.state = 729 + self.match(SqlBaseParser.PURGE) + + + pass + + elif la_ == 35: + localctx = SqlBaseParser.DropViewContext(self, localctx) + self.enterOuterAlt(localctx, 35) + self.state = 732 + self.match(SqlBaseParser.DROP) + self.state = 733 + self.match(SqlBaseParser.VIEW) + self.state = 736 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,46,self._ctx) + if la_ == 1: + self.state = 734 + self.match(SqlBaseParser.IF) + self.state = 735 + self.match(SqlBaseParser.EXISTS) + + + self.state = 738 + self.multipartIdentifier() + pass + + elif la_ == 36: + localctx = SqlBaseParser.CreateViewContext(self, localctx) + self.enterOuterAlt(localctx, 36) + self.state = 739 + self.match(SqlBaseParser.CREATE) + self.state = 742 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==181: + self.state = 740 + self.match(SqlBaseParser.OR) + self.state = 741 + self.match(SqlBaseParser.REPLACE) + + + self.state = 748 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==112 or _la==263: + self.state = 745 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==112: + self.state = 744 + self.match(SqlBaseParser.GLOBAL) + + + self.state = 747 + self.match(SqlBaseParser.TEMPORARY) + + + self.state = 750 + self.match(SqlBaseParser.VIEW) + self.state = 754 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,50,self._ctx) + if la_ == 1: + self.state = 751 + self.match(SqlBaseParser.IF) + self.state = 752 + self.match(SqlBaseParser.NOT) + self.state = 753 + self.match(SqlBaseParser.EXISTS) + + + self.state = 756 + self.multipartIdentifier() + self.state = 758 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==2: + self.state = 757 + self.identifierCommentList() + + + self.state = 768 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==45 or _la==191 or _la==262: + self.state = 766 + self._errHandler.sync(self) + token = self._input.LA(1) + if token in [45]: + self.state = 760 + self.commentSpec() + pass + elif token in [191]: + self.state = 761 + self.match(SqlBaseParser.PARTITIONED) + self.state = 762 + self.match(SqlBaseParser.ON) + self.state = 763 + self.identifierList() + pass + elif token in [262]: + self.state = 764 + self.match(SqlBaseParser.TBLPROPERTIES) + self.state = 765 + self.propertyList() + pass + else: + raise NoViableAltException(self) + + self.state = 770 + self._errHandler.sync(self) + _la = self._input.LA(1) + + self.state = 771 + self.match(SqlBaseParser.AS) + self.state = 772 + self.query() + pass + + elif la_ == 37: + localctx = SqlBaseParser.CreateTempViewUsingContext(self, localctx) + self.enterOuterAlt(localctx, 37) + self.state = 774 + self.match(SqlBaseParser.CREATE) + self.state = 777 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==181: + self.state = 775 + self.match(SqlBaseParser.OR) + self.state = 776 + self.match(SqlBaseParser.REPLACE) + + + self.state = 780 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==112: + self.state = 779 + self.match(SqlBaseParser.GLOBAL) + + + self.state = 782 + self.match(SqlBaseParser.TEMPORARY) + self.state = 783 + self.match(SqlBaseParser.VIEW) + self.state = 784 + self.tableIdentifier() + self.state = 789 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==2: + self.state = 785 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 786 + self.colTypeList() + self.state = 787 + self.match(SqlBaseParser.RIGHT_PAREN) + + + self.state = 791 + self.tableProvider() + self.state = 794 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==180: + self.state = 792 + self.match(SqlBaseParser.OPTIONS) + self.state = 793 + self.propertyList() + + + pass + + elif la_ == 38: + localctx = SqlBaseParser.AlterViewQueryContext(self, localctx) + self.enterOuterAlt(localctx, 38) + self.state = 796 + self.match(SqlBaseParser.ALTER) + self.state = 797 + self.match(SqlBaseParser.VIEW) + self.state = 798 + self.multipartIdentifier() + self.state = 800 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==20: + self.state = 799 + self.match(SqlBaseParser.AS) + + + self.state = 802 + self.query() + pass + + elif la_ == 39: + localctx = SqlBaseParser.CreateFunctionContext(self, localctx) + self.enterOuterAlt(localctx, 39) + self.state = 804 + self.match(SqlBaseParser.CREATE) + self.state = 807 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==181: + self.state = 805 + self.match(SqlBaseParser.OR) + self.state = 806 + self.match(SqlBaseParser.REPLACE) + + + self.state = 810 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==263: + self.state = 809 + self.match(SqlBaseParser.TEMPORARY) + + + self.state = 812 + self.match(SqlBaseParser.FUNCTION) + self.state = 816 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,61,self._ctx) + if la_ == 1: + self.state = 813 + self.match(SqlBaseParser.IF) + self.state = 814 + self.match(SqlBaseParser.NOT) + self.state = 815 + self.match(SqlBaseParser.EXISTS) + + + self.state = 818 + self.multipartIdentifier() + self.state = 819 + self.match(SqlBaseParser.AS) + self.state = 820 + localctx.className = self.stringLit() + self.state = 830 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==293: + self.state = 821 + self.match(SqlBaseParser.USING) + self.state = 822 + self.resource() + self.state = 827 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 823 + self.match(SqlBaseParser.COMMA) + self.state = 824 + self.resource() + self.state = 829 + self._errHandler.sync(self) + _la = self._input.LA(1) + + + + pass + + elif la_ == 40: + localctx = SqlBaseParser.DropFunctionContext(self, localctx) + self.enterOuterAlt(localctx, 40) + self.state = 832 + self.match(SqlBaseParser.DROP) + self.state = 834 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==263: + self.state = 833 + self.match(SqlBaseParser.TEMPORARY) + + + self.state = 836 + self.match(SqlBaseParser.FUNCTION) + self.state = 839 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,65,self._ctx) + if la_ == 1: + self.state = 837 + self.match(SqlBaseParser.IF) + self.state = 838 + self.match(SqlBaseParser.EXISTS) + + + self.state = 841 + self.multipartIdentifier() + pass + + elif la_ == 41: + localctx = SqlBaseParser.ExplainContext(self, localctx) + self.enterOuterAlt(localctx, 41) + self.state = 842 + self.match(SqlBaseParser.EXPLAIN) + self.state = 844 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==40 or _la==52 or ((((_la - 93)) & ~0x3f) == 0 and ((1 << (_la - 93)) & 576460752303431681) != 0): + self.state = 843 + _la = self._input.LA(1) + if not(_la==40 or _la==52 or ((((_la - 93)) & ~0x3f) == 0 and ((1 << (_la - 93)) & 576460752303431681) != 0)): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + + + self.state = 846 + self.statement() + pass + + elif la_ == 42: + localctx = SqlBaseParser.ShowTablesContext(self, localctx) + self.enterOuterAlt(localctx, 42) + self.state = 847 + self.match(SqlBaseParser.SHOW) + self.state = 848 + self.match(SqlBaseParser.TABLES) + self.state = 851 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==107 or _la==122: + self.state = 849 + _la = self._input.LA(1) + if not(_la==107 or _la==122): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 850 + self.multipartIdentifier() + + + self.state = 857 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==142 or _la==330 or _la==331: + self.state = 854 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==142: + self.state = 853 + self.match(SqlBaseParser.LIKE) + + + self.state = 856 + localctx.pattern = self.stringLit() + + + pass + + elif la_ == 43: + localctx = SqlBaseParser.ShowTableExtendedContext(self, localctx) + self.enterOuterAlt(localctx, 43) + self.state = 859 + self.match(SqlBaseParser.SHOW) + self.state = 860 + self.match(SqlBaseParser.TABLE) + self.state = 861 + self.match(SqlBaseParser.EXTENDED) + self.state = 864 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==107 or _la==122: + self.state = 862 + _la = self._input.LA(1) + if not(_la==107 or _la==122): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 863 + localctx.ns = self.multipartIdentifier() + + + self.state = 866 + self.match(SqlBaseParser.LIKE) + self.state = 867 + localctx.pattern = self.stringLit() + self.state = 869 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==190: + self.state = 868 + self.partitionSpec() + + + pass + + elif la_ == 44: + localctx = SqlBaseParser.ShowTblPropertiesContext(self, localctx) + self.enterOuterAlt(localctx, 44) + self.state = 871 + self.match(SqlBaseParser.SHOW) + self.state = 872 + self.match(SqlBaseParser.TBLPROPERTIES) + self.state = 873 + localctx.table = self.multipartIdentifier() + self.state = 878 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==2: + self.state = 874 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 875 + localctx.key = self.propertyKey() + self.state = 876 + self.match(SqlBaseParser.RIGHT_PAREN) + + + pass + + elif la_ == 45: + localctx = SqlBaseParser.ShowColumnsContext(self, localctx) + self.enterOuterAlt(localctx, 45) + self.state = 880 + self.match(SqlBaseParser.SHOW) + self.state = 881 + self.match(SqlBaseParser.COLUMNS) + self.state = 882 + _la = self._input.LA(1) + if not(_la==107 or _la==122): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 883 + localctx.table = self.multipartIdentifier() + self.state = 886 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==107 or _la==122: + self.state = 884 + _la = self._input.LA(1) + if not(_la==107 or _la==122): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 885 + localctx.ns = self.multipartIdentifier() + + + pass + + elif la_ == 46: + localctx = SqlBaseParser.ShowViewsContext(self, localctx) + self.enterOuterAlt(localctx, 46) + self.state = 888 + self.match(SqlBaseParser.SHOW) + self.state = 889 + self.match(SqlBaseParser.VIEWS) + self.state = 892 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==107 or _la==122: + self.state = 890 + _la = self._input.LA(1) + if not(_la==107 or _la==122): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 891 + self.multipartIdentifier() + + + self.state = 898 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==142 or _la==330 or _la==331: + self.state = 895 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==142: + self.state = 894 + self.match(SqlBaseParser.LIKE) + + + self.state = 897 + localctx.pattern = self.stringLit() + + + pass + + elif la_ == 47: + localctx = SqlBaseParser.ShowPartitionsContext(self, localctx) + self.enterOuterAlt(localctx, 47) + self.state = 900 + self.match(SqlBaseParser.SHOW) + self.state = 901 + self.match(SqlBaseParser.PARTITIONS) + self.state = 902 + self.multipartIdentifier() + self.state = 904 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==190: + self.state = 903 + self.partitionSpec() + + + pass + + elif la_ == 48: + localctx = SqlBaseParser.ShowFunctionsContext(self, localctx) + self.enterOuterAlt(localctx, 48) + self.state = 906 + self.match(SqlBaseParser.SHOW) + self.state = 908 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,78,self._ctx) + if la_ == 1: + self.state = 907 + self.identifier() + + + self.state = 910 + self.match(SqlBaseParser.FUNCTIONS) + self.state = 913 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,79,self._ctx) + if la_ == 1: + self.state = 911 + _la = self._input.LA(1) + if not(_la==107 or _la==122): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 912 + localctx.ns = self.multipartIdentifier() + + + self.state = 922 + self._errHandler.sync(self) + _la = self._input.LA(1) + if (((_la) & ~0x3f) == 0 and ((1 << _la) & -256) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & -1) != 0) or ((((_la - 128)) & ~0x3f) == 0 and ((1 << (_la - 128)) & -1) != 0) or ((((_la - 192)) & ~0x3f) == 0 and ((1 << (_la - 192)) & -1) != 0) or ((((_la - 256)) & ~0x3f) == 0 and ((1 << (_la - 256)) & 4503599627370495) != 0) or ((((_la - 330)) & ~0x3f) == 0 and ((1 << (_la - 330)) & 6147) != 0): + self.state = 916 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,80,self._ctx) + if la_ == 1: + self.state = 915 + self.match(SqlBaseParser.LIKE) + + + self.state = 920 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,81,self._ctx) + if la_ == 1: + self.state = 918 + localctx.legacy = self.multipartIdentifier() + pass + + elif la_ == 2: + self.state = 919 + localctx.pattern = self.stringLit() + pass + + + + + pass + + elif la_ == 49: + localctx = SqlBaseParser.ShowCreateTableContext(self, localctx) + self.enterOuterAlt(localctx, 49) + self.state = 924 + self.match(SqlBaseParser.SHOW) + self.state = 925 + self.match(SqlBaseParser.CREATE) + self.state = 926 + self.match(SqlBaseParser.TABLE) + self.state = 927 + self.multipartIdentifier() + self.state = 930 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==20: + self.state = 928 + self.match(SqlBaseParser.AS) + self.state = 929 + self.match(SqlBaseParser.SERDE) + + + pass + + elif la_ == 50: + localctx = SqlBaseParser.ShowCurrentNamespaceContext(self, localctx) + self.enterOuterAlt(localctx, 50) + self.state = 932 + self.match(SqlBaseParser.SHOW) + self.state = 933 + self.match(SqlBaseParser.CURRENT) + self.state = 934 + self.namespace() + pass + + elif la_ == 51: + localctx = SqlBaseParser.ShowCatalogsContext(self, localctx) + self.enterOuterAlt(localctx, 51) + self.state = 935 + self.match(SqlBaseParser.SHOW) + self.state = 936 + self.match(SqlBaseParser.CATALOGS) + self.state = 941 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==142 or _la==330 or _la==331: + self.state = 938 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==142: + self.state = 937 + self.match(SqlBaseParser.LIKE) + + + self.state = 940 + localctx.pattern = self.stringLit() + + + pass + + elif la_ == 52: + localctx = SqlBaseParser.DescribeFunctionContext(self, localctx) + self.enterOuterAlt(localctx, 52) + self.state = 943 + _la = self._input.LA(1) + if not(_la==74 or _la==75): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 944 + self.match(SqlBaseParser.FUNCTION) + self.state = 946 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,86,self._ctx) + if la_ == 1: + self.state = 945 + self.match(SqlBaseParser.EXTENDED) + + + self.state = 948 + self.describeFuncName() + pass + + elif la_ == 53: + localctx = SqlBaseParser.DescribeNamespaceContext(self, localctx) + self.enterOuterAlt(localctx, 53) + self.state = 949 + _la = self._input.LA(1) + if not(_la==74 or _la==75): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 950 + self.namespace() + self.state = 952 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,87,self._ctx) + if la_ == 1: + self.state = 951 + self.match(SqlBaseParser.EXTENDED) + + + self.state = 954 + self.multipartIdentifier() + pass + + elif la_ == 54: + localctx = SqlBaseParser.DescribeRelationContext(self, localctx) + self.enterOuterAlt(localctx, 54) + self.state = 956 + _la = self._input.LA(1) + if not(_la==74 or _la==75): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 958 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,88,self._ctx) + if la_ == 1: + self.state = 957 + self.match(SqlBaseParser.TABLE) + + + self.state = 961 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,89,self._ctx) + if la_ == 1: + self.state = 960 + localctx.option = self._input.LT(1) + _la = self._input.LA(1) + if not(_la==93 or _la==106): + localctx.option = self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + + + self.state = 963 + self.multipartIdentifier() + self.state = 965 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,90,self._ctx) + if la_ == 1: + self.state = 964 + self.partitionSpec() + + + self.state = 968 + self._errHandler.sync(self) + _la = self._input.LA(1) + if (((_la) & ~0x3f) == 0 and ((1 << _la) & -256) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & -1) != 0) or ((((_la - 128)) & ~0x3f) == 0 and ((1 << (_la - 128)) & -1) != 0) or ((((_la - 192)) & ~0x3f) == 0 and ((1 << (_la - 192)) & -1) != 0) or ((((_la - 256)) & ~0x3f) == 0 and ((1 << (_la - 256)) & 4503599627370495) != 0) or ((((_la - 331)) & ~0x3f) == 0 and ((1 << (_la - 331)) & 3073) != 0): + self.state = 967 + self.describeColName() + + + pass + + elif la_ == 55: + localctx = SqlBaseParser.DescribeQueryContext(self, localctx) + self.enterOuterAlt(localctx, 55) + self.state = 970 + _la = self._input.LA(1) + if not(_la==74 or _la==75): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 972 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==205: + self.state = 971 + self.match(SqlBaseParser.QUERY) + + + self.state = 974 + self.query() + pass + + elif la_ == 56: + localctx = SqlBaseParser.CommentNamespaceContext(self, localctx) + self.enterOuterAlt(localctx, 56) + self.state = 975 + self.match(SqlBaseParser.COMMENT) + self.state = 976 + self.match(SqlBaseParser.ON) + self.state = 977 + self.namespace() + self.state = 978 + self.multipartIdentifier() + self.state = 979 + self.match(SqlBaseParser.IS) + self.state = 980 + self.comment() + pass + + elif la_ == 57: + localctx = SqlBaseParser.CommentTableContext(self, localctx) + self.enterOuterAlt(localctx, 57) + self.state = 982 + self.match(SqlBaseParser.COMMENT) + self.state = 983 + self.match(SqlBaseParser.ON) + self.state = 984 + self.match(SqlBaseParser.TABLE) + self.state = 985 + self.multipartIdentifier() + self.state = 986 + self.match(SqlBaseParser.IS) + self.state = 987 + self.comment() + pass + + elif la_ == 58: + localctx = SqlBaseParser.RefreshTableContext(self, localctx) + self.enterOuterAlt(localctx, 58) + self.state = 989 + self.match(SqlBaseParser.REFRESH) + self.state = 990 + self.match(SqlBaseParser.TABLE) + self.state = 991 + self.multipartIdentifier() + pass + + elif la_ == 59: + localctx = SqlBaseParser.RefreshFunctionContext(self, localctx) + self.enterOuterAlt(localctx, 59) + self.state = 992 + self.match(SqlBaseParser.REFRESH) + self.state = 993 + self.match(SqlBaseParser.FUNCTION) + self.state = 994 + self.multipartIdentifier() + pass + + elif la_ == 60: + localctx = SqlBaseParser.RefreshResourceContext(self, localctx) + self.enterOuterAlt(localctx, 60) + self.state = 995 + self.match(SqlBaseParser.REFRESH) + self.state = 1003 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,94,self._ctx) + if la_ == 1: + self.state = 996 + self.stringLit() + pass + + elif la_ == 2: + self.state = 1000 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,93,self._ctx) + while _alt!=1 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt==1+1: + self.state = 997 + self.matchWildcard() + self.state = 1002 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,93,self._ctx) + + pass + + + pass + + elif la_ == 61: + localctx = SqlBaseParser.CacheTableContext(self, localctx) + self.enterOuterAlt(localctx, 61) + self.state = 1005 + self.match(SqlBaseParser.CACHE) + self.state = 1007 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==139: + self.state = 1006 + self.match(SqlBaseParser.LAZY) + + + self.state = 1009 + self.match(SqlBaseParser.TABLE) + self.state = 1010 + self.multipartIdentifier() + self.state = 1013 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==180: + self.state = 1011 + self.match(SqlBaseParser.OPTIONS) + self.state = 1012 + localctx.options = self.propertyList() + + + self.state = 1019 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==2 or _la==20 or _la==107 or _la==154 or ((((_la - 210)) & ~0x3f) == 0 and ((1 << (_la - 210)) & 281474985099265) != 0) or _la==294 or _la==303: + self.state = 1016 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==20: + self.state = 1015 + self.match(SqlBaseParser.AS) + + + self.state = 1018 + self.query() + + + pass + + elif la_ == 62: + localctx = SqlBaseParser.UncacheTableContext(self, localctx) + self.enterOuterAlt(localctx, 62) + self.state = 1021 + self.match(SqlBaseParser.UNCACHE) + self.state = 1022 + self.match(SqlBaseParser.TABLE) + self.state = 1025 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,99,self._ctx) + if la_ == 1: + self.state = 1023 + self.match(SqlBaseParser.IF) + self.state = 1024 + self.match(SqlBaseParser.EXISTS) + + + self.state = 1027 + self.multipartIdentifier() + pass + + elif la_ == 63: + localctx = SqlBaseParser.ClearCacheContext(self, localctx) + self.enterOuterAlt(localctx, 63) + self.state = 1028 + self.match(SqlBaseParser.CLEAR) + self.state = 1029 + self.match(SqlBaseParser.CACHE) + pass + + elif la_ == 64: + localctx = SqlBaseParser.LoadDataContext(self, localctx) + self.enterOuterAlt(localctx, 64) + self.state = 1030 + self.match(SqlBaseParser.LOAD) + self.state = 1031 + self.match(SqlBaseParser.DATA) + self.state = 1033 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==148: + self.state = 1032 + self.match(SqlBaseParser.LOCAL) + + + self.state = 1035 + self.match(SqlBaseParser.INPATH) + self.state = 1036 + localctx.path = self.stringLit() + self.state = 1038 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==189: + self.state = 1037 + self.match(SqlBaseParser.OVERWRITE) + + + self.state = 1040 + self.match(SqlBaseParser.INTO) + self.state = 1041 + self.match(SqlBaseParser.TABLE) + self.state = 1042 + self.multipartIdentifier() + self.state = 1044 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==190: + self.state = 1043 + self.partitionSpec() + + + pass + + elif la_ == 65: + localctx = SqlBaseParser.TruncateTableContext(self, localctx) + self.enterOuterAlt(localctx, 65) + self.state = 1046 + self.match(SqlBaseParser.TRUNCATE) + self.state = 1047 + self.match(SqlBaseParser.TABLE) + self.state = 1048 + self.multipartIdentifier() + self.state = 1050 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==190: + self.state = 1049 + self.partitionSpec() + + + pass + + elif la_ == 66: + localctx = SqlBaseParser.RepairTableContext(self, localctx) + self.enterOuterAlt(localctx, 66) + self.state = 1053 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==165: + self.state = 1052 + self.match(SqlBaseParser.MSCK) + + + self.state = 1055 + self.match(SqlBaseParser.REPAIR) + self.state = 1056 + self.match(SqlBaseParser.TABLE) + self.state = 1057 + self.multipartIdentifier() + self.state = 1060 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==8 or _la==82 or _la==255: + self.state = 1058 + localctx.option = self._input.LT(1) + _la = self._input.LA(1) + if not(_la==8 or _la==82 or _la==255): + localctx.option = self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 1059 + self.match(SqlBaseParser.PARTITIONS) + + + pass + + elif la_ == 67: + localctx = SqlBaseParser.ManageResourceContext(self, localctx) + self.enterOuterAlt(localctx, 67) + self.state = 1062 + localctx.op = self._input.LT(1) + _la = self._input.LA(1) + if not(_la==8 or _la==146): + localctx.op = self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 1063 + self.identifier() + self.state = 1067 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,106,self._ctx) + while _alt!=1 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt==1+1: + self.state = 1064 + self.matchWildcard() + self.state = 1069 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,106,self._ctx) + + pass + + elif la_ == 68: + localctx = SqlBaseParser.FailNativeCommandContext(self, localctx) + self.enterOuterAlt(localctx, 68) + self.state = 1070 + self.match(SqlBaseParser.SET) + self.state = 1071 + self.match(SqlBaseParser.ROLE) + self.state = 1075 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,107,self._ctx) + while _alt!=1 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt==1+1: + self.state = 1072 + self.matchWildcard() + self.state = 1077 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,107,self._ctx) + + pass + + elif la_ == 69: + localctx = SqlBaseParser.SetTimeZoneContext(self, localctx) + self.enterOuterAlt(localctx, 69) + self.state = 1078 + self.match(SqlBaseParser.SET) + self.state = 1079 + self.match(SqlBaseParser.TIME) + self.state = 1080 + self.match(SqlBaseParser.ZONE) + self.state = 1081 + self.interval() + pass + + elif la_ == 70: + localctx = SqlBaseParser.SetTimeZoneContext(self, localctx) + self.enterOuterAlt(localctx, 70) + self.state = 1082 + self.match(SqlBaseParser.SET) + self.state = 1083 + self.match(SqlBaseParser.TIME) + self.state = 1084 + self.match(SqlBaseParser.ZONE) + self.state = 1085 + self.timezone() + pass + + elif la_ == 71: + localctx = SqlBaseParser.SetTimeZoneContext(self, localctx) + self.enterOuterAlt(localctx, 71) + self.state = 1086 + self.match(SqlBaseParser.SET) + self.state = 1087 + self.match(SqlBaseParser.TIME) + self.state = 1088 + self.match(SqlBaseParser.ZONE) + self.state = 1092 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,108,self._ctx) + while _alt!=1 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt==1+1: + self.state = 1089 + self.matchWildcard() + self.state = 1094 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,108,self._ctx) + + pass + + elif la_ == 72: + localctx = SqlBaseParser.SetQuotedConfigurationContext(self, localctx) + self.enterOuterAlt(localctx, 72) + self.state = 1095 + self.match(SqlBaseParser.SET) + self.state = 1096 + self.configKey() + self.state = 1097 + self.match(SqlBaseParser.EQ) + self.state = 1098 + self.configValue() + pass + + elif la_ == 73: + localctx = SqlBaseParser.SetConfigurationContext(self, localctx) + self.enterOuterAlt(localctx, 73) + self.state = 1100 + self.match(SqlBaseParser.SET) + self.state = 1101 + self.configKey() + self.state = 1109 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==308: + self.state = 1102 + self.match(SqlBaseParser.EQ) + self.state = 1106 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,109,self._ctx) + while _alt!=1 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt==1+1: + self.state = 1103 + self.matchWildcard() + self.state = 1108 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,109,self._ctx) + + + + pass + + elif la_ == 74: + localctx = SqlBaseParser.SetQuotedConfigurationContext(self, localctx) + self.enterOuterAlt(localctx, 74) + self.state = 1111 + self.match(SqlBaseParser.SET) + self.state = 1115 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,111,self._ctx) + while _alt!=1 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt==1+1: + self.state = 1112 + self.matchWildcard() + self.state = 1117 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,111,self._ctx) + + self.state = 1118 + self.match(SqlBaseParser.EQ) + self.state = 1119 + self.configValue() + pass + + elif la_ == 75: + localctx = SqlBaseParser.SetConfigurationContext(self, localctx) + self.enterOuterAlt(localctx, 75) + self.state = 1120 + self.match(SqlBaseParser.SET) + self.state = 1124 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,112,self._ctx) + while _alt!=1 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt==1+1: + self.state = 1121 + self.matchWildcard() + self.state = 1126 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,112,self._ctx) + + pass + + elif la_ == 76: + localctx = SqlBaseParser.ResetQuotedConfigurationContext(self, localctx) + self.enterOuterAlt(localctx, 76) + self.state = 1127 + self.match(SqlBaseParser.RESET) + self.state = 1128 + self.configKey() + pass + + elif la_ == 77: + localctx = SqlBaseParser.ResetConfigurationContext(self, localctx) + self.enterOuterAlt(localctx, 77) + self.state = 1129 + self.match(SqlBaseParser.RESET) + self.state = 1133 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,113,self._ctx) + while _alt!=1 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt==1+1: + self.state = 1130 + self.matchWildcard() + self.state = 1135 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,113,self._ctx) + + pass + + elif la_ == 78: + localctx = SqlBaseParser.CreateIndexContext(self, localctx) + self.enterOuterAlt(localctx, 78) + self.state = 1136 + self.match(SqlBaseParser.CREATE) + self.state = 1137 + self.match(SqlBaseParser.INDEX) + self.state = 1141 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,114,self._ctx) + if la_ == 1: + self.state = 1138 + self.match(SqlBaseParser.IF) + self.state = 1139 + self.match(SqlBaseParser.NOT) + self.state = 1140 + self.match(SqlBaseParser.EXISTS) + + + self.state = 1143 + self.identifier() + self.state = 1144 + self.match(SqlBaseParser.ON) + self.state = 1146 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,115,self._ctx) + if la_ == 1: + self.state = 1145 + self.match(SqlBaseParser.TABLE) + + + self.state = 1148 + self.multipartIdentifier() + self.state = 1151 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==293: + self.state = 1149 + self.match(SqlBaseParser.USING) + self.state = 1150 + localctx.indexType = self.identifier() + + + self.state = 1153 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 1154 + localctx.columns = self.multipartIdentifierPropertyList() + self.state = 1155 + self.match(SqlBaseParser.RIGHT_PAREN) + self.state = 1158 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==180: + self.state = 1156 + self.match(SqlBaseParser.OPTIONS) + self.state = 1157 + localctx.options = self.propertyList() + + + pass + + elif la_ == 79: + localctx = SqlBaseParser.DropIndexContext(self, localctx) + self.enterOuterAlt(localctx, 79) + self.state = 1160 + self.match(SqlBaseParser.DROP) + self.state = 1161 + self.match(SqlBaseParser.INDEX) + self.state = 1164 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,118,self._ctx) + if la_ == 1: + self.state = 1162 + self.match(SqlBaseParser.IF) + self.state = 1163 + self.match(SqlBaseParser.EXISTS) + + + self.state = 1166 + self.identifier() + self.state = 1167 + self.match(SqlBaseParser.ON) + self.state = 1169 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,119,self._ctx) + if la_ == 1: + self.state = 1168 + self.match(SqlBaseParser.TABLE) + + + self.state = 1171 + self.multipartIdentifier() + pass + + elif la_ == 80: + localctx = SqlBaseParser.FailNativeCommandContext(self, localctx) + self.enterOuterAlt(localctx, 80) + self.state = 1173 + self.unsupportedHiveNativeCommands() + self.state = 1177 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,120,self._ctx) + while _alt!=1 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt==1+1: + self.state = 1174 + self.matchWildcard() + self.state = 1179 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,120,self._ctx) + + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class TimezoneContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def stringLit(self): + return self.getTypedRuleContext(SqlBaseParser.StringLitContext,0) + + + def LOCAL(self): + return self.getToken(SqlBaseParser.LOCAL, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_timezone + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterTimezone" ): + listener.enterTimezone(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitTimezone" ): + listener.exitTimezone(self) + + + + + def timezone(self): + + localctx = SqlBaseParser.TimezoneContext(self, self._ctx, self.state) + self.enterRule(localctx, 16, self.RULE_timezone) + try: + self.state = 1184 + self._errHandler.sync(self) + token = self._input.LA(1) + if token in [330, 331]: + self.enterOuterAlt(localctx, 1) + self.state = 1182 + self.stringLit() + pass + elif token in [148]: + self.enterOuterAlt(localctx, 2) + self.state = 1183 + self.match(SqlBaseParser.LOCAL) + pass + else: + raise NoViableAltException(self) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class ConfigKeyContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def quotedIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.QuotedIdentifierContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_configKey + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterConfigKey" ): + listener.enterConfigKey(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitConfigKey" ): + listener.exitConfigKey(self) + + + + + def configKey(self): + + localctx = SqlBaseParser.ConfigKeyContext(self, self._ctx, self.state) + self.enterRule(localctx, 18, self.RULE_configKey) + try: + self.enterOuterAlt(localctx, 1) + self.state = 1186 + self.quotedIdentifier() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class ConfigValueContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def backQuotedIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.BackQuotedIdentifierContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_configValue + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterConfigValue" ): + listener.enterConfigValue(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitConfigValue" ): + listener.exitConfigValue(self) + + + + + def configValue(self): + + localctx = SqlBaseParser.ConfigValueContext(self, self._ctx, self.state) + self.enterRule(localctx, 20, self.RULE_configValue) + try: + self.enterOuterAlt(localctx, 1) + self.state = 1188 + self.backQuotedIdentifier() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class UnsupportedHiveNativeCommandsContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self.kw1 = None # Token + self.kw2 = None # Token + self.kw3 = None # Token + self.kw4 = None # Token + self.kw5 = None # Token + self.kw6 = None # Token + + def CREATE(self): + return self.getToken(SqlBaseParser.CREATE, 0) + + def ROLE(self): + return self.getToken(SqlBaseParser.ROLE, 0) + + def DROP(self): + return self.getToken(SqlBaseParser.DROP, 0) + + def GRANT(self): + return self.getToken(SqlBaseParser.GRANT, 0) + + def REVOKE(self): + return self.getToken(SqlBaseParser.REVOKE, 0) + + def SHOW(self): + return self.getToken(SqlBaseParser.SHOW, 0) + + def PRINCIPALS(self): + return self.getToken(SqlBaseParser.PRINCIPALS, 0) + + def ROLES(self): + return self.getToken(SqlBaseParser.ROLES, 0) + + def CURRENT(self): + return self.getToken(SqlBaseParser.CURRENT, 0) + + def EXPORT(self): + return self.getToken(SqlBaseParser.EXPORT, 0) + + def TABLE(self): + return self.getToken(SqlBaseParser.TABLE, 0) + + def IMPORT(self): + return self.getToken(SqlBaseParser.IMPORT, 0) + + def COMPACTIONS(self): + return self.getToken(SqlBaseParser.COMPACTIONS, 0) + + def TRANSACTIONS(self): + return self.getToken(SqlBaseParser.TRANSACTIONS, 0) + + def INDEXES(self): + return self.getToken(SqlBaseParser.INDEXES, 0) + + def LOCKS(self): + return self.getToken(SqlBaseParser.LOCKS, 0) + + def INDEX(self): + return self.getToken(SqlBaseParser.INDEX, 0) + + def ALTER(self): + return self.getToken(SqlBaseParser.ALTER, 0) + + def LOCK(self): + return self.getToken(SqlBaseParser.LOCK, 0) + + def DATABASE(self): + return self.getToken(SqlBaseParser.DATABASE, 0) + + def UNLOCK(self): + return self.getToken(SqlBaseParser.UNLOCK, 0) + + def TEMPORARY(self): + return self.getToken(SqlBaseParser.TEMPORARY, 0) + + def MACRO(self): + return self.getToken(SqlBaseParser.MACRO, 0) + + def tableIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.TableIdentifierContext,0) + + + def NOT(self): + return self.getToken(SqlBaseParser.NOT, 0) + + def CLUSTERED(self): + return self.getToken(SqlBaseParser.CLUSTERED, 0) + + def BY(self): + return self.getToken(SqlBaseParser.BY, 0) + + def SORTED(self): + return self.getToken(SqlBaseParser.SORTED, 0) + + def SKEWED(self): + return self.getToken(SqlBaseParser.SKEWED, 0) + + def STORED(self): + return self.getToken(SqlBaseParser.STORED, 0) + + def AS(self): + return self.getToken(SqlBaseParser.AS, 0) + + def DIRECTORIES(self): + return self.getToken(SqlBaseParser.DIRECTORIES, 0) + + def SET(self): + return self.getToken(SqlBaseParser.SET, 0) + + def LOCATION(self): + return self.getToken(SqlBaseParser.LOCATION, 0) + + def EXCHANGE(self): + return self.getToken(SqlBaseParser.EXCHANGE, 0) + + def PARTITION(self): + return self.getToken(SqlBaseParser.PARTITION, 0) + + def ARCHIVE(self): + return self.getToken(SqlBaseParser.ARCHIVE, 0) + + def UNARCHIVE(self): + return self.getToken(SqlBaseParser.UNARCHIVE, 0) + + def TOUCH(self): + return self.getToken(SqlBaseParser.TOUCH, 0) + + def COMPACT(self): + return self.getToken(SqlBaseParser.COMPACT, 0) + + def partitionSpec(self): + return self.getTypedRuleContext(SqlBaseParser.PartitionSpecContext,0) + + + def CONCATENATE(self): + return self.getToken(SqlBaseParser.CONCATENATE, 0) + + def FILEFORMAT(self): + return self.getToken(SqlBaseParser.FILEFORMAT, 0) + + def REPLACE(self): + return self.getToken(SqlBaseParser.REPLACE, 0) + + def COLUMNS(self): + return self.getToken(SqlBaseParser.COLUMNS, 0) + + def START(self): + return self.getToken(SqlBaseParser.START, 0) + + def TRANSACTION(self): + return self.getToken(SqlBaseParser.TRANSACTION, 0) + + def COMMIT(self): + return self.getToken(SqlBaseParser.COMMIT, 0) + + def ROLLBACK(self): + return self.getToken(SqlBaseParser.ROLLBACK, 0) + + def DFS(self): + return self.getToken(SqlBaseParser.DFS, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_unsupportedHiveNativeCommands + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterUnsupportedHiveNativeCommands" ): + listener.enterUnsupportedHiveNativeCommands(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitUnsupportedHiveNativeCommands" ): + listener.exitUnsupportedHiveNativeCommands(self) + + + + + def unsupportedHiveNativeCommands(self): + + localctx = SqlBaseParser.UnsupportedHiveNativeCommandsContext(self, self._ctx, self.state) + self.enterRule(localctx, 22, self.RULE_unsupportedHiveNativeCommands) + self._la = 0 # Token type + try: + self.state = 1358 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,130,self._ctx) + if la_ == 1: + self.enterOuterAlt(localctx, 1) + self.state = 1190 + localctx.kw1 = self.match(SqlBaseParser.CREATE) + self.state = 1191 + localctx.kw2 = self.match(SqlBaseParser.ROLE) + pass + + elif la_ == 2: + self.enterOuterAlt(localctx, 2) + self.state = 1192 + localctx.kw1 = self.match(SqlBaseParser.DROP) + self.state = 1193 + localctx.kw2 = self.match(SqlBaseParser.ROLE) + pass + + elif la_ == 3: + self.enterOuterAlt(localctx, 3) + self.state = 1194 + localctx.kw1 = self.match(SqlBaseParser.GRANT) + self.state = 1196 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,123,self._ctx) + if la_ == 1: + self.state = 1195 + localctx.kw2 = self.match(SqlBaseParser.ROLE) + + + pass + + elif la_ == 4: + self.enterOuterAlt(localctx, 4) + self.state = 1198 + localctx.kw1 = self.match(SqlBaseParser.REVOKE) + self.state = 1200 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,124,self._ctx) + if la_ == 1: + self.state = 1199 + localctx.kw2 = self.match(SqlBaseParser.ROLE) + + + pass + + elif la_ == 5: + self.enterOuterAlt(localctx, 5) + self.state = 1202 + localctx.kw1 = self.match(SqlBaseParser.SHOW) + self.state = 1203 + localctx.kw2 = self.match(SqlBaseParser.GRANT) + pass + + elif la_ == 6: + self.enterOuterAlt(localctx, 6) + self.state = 1204 + localctx.kw1 = self.match(SqlBaseParser.SHOW) + self.state = 1205 + localctx.kw2 = self.match(SqlBaseParser.ROLE) + self.state = 1207 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,125,self._ctx) + if la_ == 1: + self.state = 1206 + localctx.kw3 = self.match(SqlBaseParser.GRANT) + + + pass + + elif la_ == 7: + self.enterOuterAlt(localctx, 7) + self.state = 1209 + localctx.kw1 = self.match(SqlBaseParser.SHOW) + self.state = 1210 + localctx.kw2 = self.match(SqlBaseParser.PRINCIPALS) + pass + + elif la_ == 8: + self.enterOuterAlt(localctx, 8) + self.state = 1211 + localctx.kw1 = self.match(SqlBaseParser.SHOW) + self.state = 1212 + localctx.kw2 = self.match(SqlBaseParser.ROLES) + pass + + elif la_ == 9: + self.enterOuterAlt(localctx, 9) + self.state = 1213 + localctx.kw1 = self.match(SqlBaseParser.SHOW) + self.state = 1214 + localctx.kw2 = self.match(SqlBaseParser.CURRENT) + self.state = 1215 + localctx.kw3 = self.match(SqlBaseParser.ROLES) + pass + + elif la_ == 10: + self.enterOuterAlt(localctx, 10) + self.state = 1216 + localctx.kw1 = self.match(SqlBaseParser.EXPORT) + self.state = 1217 + localctx.kw2 = self.match(SqlBaseParser.TABLE) + pass + + elif la_ == 11: + self.enterOuterAlt(localctx, 11) + self.state = 1218 + localctx.kw1 = self.match(SqlBaseParser.IMPORT) + self.state = 1219 + localctx.kw2 = self.match(SqlBaseParser.TABLE) + pass + + elif la_ == 12: + self.enterOuterAlt(localctx, 12) + self.state = 1220 + localctx.kw1 = self.match(SqlBaseParser.SHOW) + self.state = 1221 + localctx.kw2 = self.match(SqlBaseParser.COMPACTIONS) + pass + + elif la_ == 13: + self.enterOuterAlt(localctx, 13) + self.state = 1222 + localctx.kw1 = self.match(SqlBaseParser.SHOW) + self.state = 1223 + localctx.kw2 = self.match(SqlBaseParser.CREATE) + self.state = 1224 + localctx.kw3 = self.match(SqlBaseParser.TABLE) + pass + + elif la_ == 14: + self.enterOuterAlt(localctx, 14) + self.state = 1225 + localctx.kw1 = self.match(SqlBaseParser.SHOW) + self.state = 1226 + localctx.kw2 = self.match(SqlBaseParser.TRANSACTIONS) + pass + + elif la_ == 15: + self.enterOuterAlt(localctx, 15) + self.state = 1227 + localctx.kw1 = self.match(SqlBaseParser.SHOW) + self.state = 1228 + localctx.kw2 = self.match(SqlBaseParser.INDEXES) + pass + + elif la_ == 16: + self.enterOuterAlt(localctx, 16) + self.state = 1229 + localctx.kw1 = self.match(SqlBaseParser.SHOW) + self.state = 1230 + localctx.kw2 = self.match(SqlBaseParser.LOCKS) + pass + + elif la_ == 17: + self.enterOuterAlt(localctx, 17) + self.state = 1231 + localctx.kw1 = self.match(SqlBaseParser.CREATE) + self.state = 1232 + localctx.kw2 = self.match(SqlBaseParser.INDEX) + pass + + elif la_ == 18: + self.enterOuterAlt(localctx, 18) + self.state = 1233 + localctx.kw1 = self.match(SqlBaseParser.DROP) + self.state = 1234 + localctx.kw2 = self.match(SqlBaseParser.INDEX) + pass + + elif la_ == 19: + self.enterOuterAlt(localctx, 19) + self.state = 1235 + localctx.kw1 = self.match(SqlBaseParser.ALTER) + self.state = 1236 + localctx.kw2 = self.match(SqlBaseParser.INDEX) + pass + + elif la_ == 20: + self.enterOuterAlt(localctx, 20) + self.state = 1237 + localctx.kw1 = self.match(SqlBaseParser.LOCK) + self.state = 1238 + localctx.kw2 = self.match(SqlBaseParser.TABLE) + pass + + elif la_ == 21: + self.enterOuterAlt(localctx, 21) + self.state = 1239 + localctx.kw1 = self.match(SqlBaseParser.LOCK) + self.state = 1240 + localctx.kw2 = self.match(SqlBaseParser.DATABASE) + pass + + elif la_ == 22: + self.enterOuterAlt(localctx, 22) + self.state = 1241 + localctx.kw1 = self.match(SqlBaseParser.UNLOCK) + self.state = 1242 + localctx.kw2 = self.match(SqlBaseParser.TABLE) + pass + + elif la_ == 23: + self.enterOuterAlt(localctx, 23) + self.state = 1243 + localctx.kw1 = self.match(SqlBaseParser.UNLOCK) + self.state = 1244 + localctx.kw2 = self.match(SqlBaseParser.DATABASE) + pass + + elif la_ == 24: + self.enterOuterAlt(localctx, 24) + self.state = 1245 + localctx.kw1 = self.match(SqlBaseParser.CREATE) + self.state = 1246 + localctx.kw2 = self.match(SqlBaseParser.TEMPORARY) + self.state = 1247 + localctx.kw3 = self.match(SqlBaseParser.MACRO) + pass + + elif la_ == 25: + self.enterOuterAlt(localctx, 25) + self.state = 1248 + localctx.kw1 = self.match(SqlBaseParser.DROP) + self.state = 1249 + localctx.kw2 = self.match(SqlBaseParser.TEMPORARY) + self.state = 1250 + localctx.kw3 = self.match(SqlBaseParser.MACRO) + pass + + elif la_ == 26: + self.enterOuterAlt(localctx, 26) + self.state = 1251 + localctx.kw1 = self.match(SqlBaseParser.ALTER) + self.state = 1252 + localctx.kw2 = self.match(SqlBaseParser.TABLE) + self.state = 1253 + self.tableIdentifier() + self.state = 1254 + localctx.kw3 = self.match(SqlBaseParser.NOT) + self.state = 1255 + localctx.kw4 = self.match(SqlBaseParser.CLUSTERED) + pass + + elif la_ == 27: + self.enterOuterAlt(localctx, 27) + self.state = 1257 + localctx.kw1 = self.match(SqlBaseParser.ALTER) + self.state = 1258 + localctx.kw2 = self.match(SqlBaseParser.TABLE) + self.state = 1259 + self.tableIdentifier() + self.state = 1260 + localctx.kw3 = self.match(SqlBaseParser.CLUSTERED) + self.state = 1261 + localctx.kw4 = self.match(SqlBaseParser.BY) + pass + + elif la_ == 28: + self.enterOuterAlt(localctx, 28) + self.state = 1263 + localctx.kw1 = self.match(SqlBaseParser.ALTER) + self.state = 1264 + localctx.kw2 = self.match(SqlBaseParser.TABLE) + self.state = 1265 + self.tableIdentifier() + self.state = 1266 + localctx.kw3 = self.match(SqlBaseParser.NOT) + self.state = 1267 + localctx.kw4 = self.match(SqlBaseParser.SORTED) + pass + + elif la_ == 29: + self.enterOuterAlt(localctx, 29) + self.state = 1269 + localctx.kw1 = self.match(SqlBaseParser.ALTER) + self.state = 1270 + localctx.kw2 = self.match(SqlBaseParser.TABLE) + self.state = 1271 + self.tableIdentifier() + self.state = 1272 + localctx.kw3 = self.match(SqlBaseParser.SKEWED) + self.state = 1273 + localctx.kw4 = self.match(SqlBaseParser.BY) + pass + + elif la_ == 30: + self.enterOuterAlt(localctx, 30) + self.state = 1275 + localctx.kw1 = self.match(SqlBaseParser.ALTER) + self.state = 1276 + localctx.kw2 = self.match(SqlBaseParser.TABLE) + self.state = 1277 + self.tableIdentifier() + self.state = 1278 + localctx.kw3 = self.match(SqlBaseParser.NOT) + self.state = 1279 + localctx.kw4 = self.match(SqlBaseParser.SKEWED) + pass + + elif la_ == 31: + self.enterOuterAlt(localctx, 31) + self.state = 1281 + localctx.kw1 = self.match(SqlBaseParser.ALTER) + self.state = 1282 + localctx.kw2 = self.match(SqlBaseParser.TABLE) + self.state = 1283 + self.tableIdentifier() + self.state = 1284 + localctx.kw3 = self.match(SqlBaseParser.NOT) + self.state = 1285 + localctx.kw4 = self.match(SqlBaseParser.STORED) + self.state = 1286 + localctx.kw5 = self.match(SqlBaseParser.AS) + self.state = 1287 + localctx.kw6 = self.match(SqlBaseParser.DIRECTORIES) + pass + + elif la_ == 32: + self.enterOuterAlt(localctx, 32) + self.state = 1289 + localctx.kw1 = self.match(SqlBaseParser.ALTER) + self.state = 1290 + localctx.kw2 = self.match(SqlBaseParser.TABLE) + self.state = 1291 + self.tableIdentifier() + self.state = 1292 + localctx.kw3 = self.match(SqlBaseParser.SET) + self.state = 1293 + localctx.kw4 = self.match(SqlBaseParser.SKEWED) + self.state = 1294 + localctx.kw5 = self.match(SqlBaseParser.LOCATION) + pass + + elif la_ == 33: + self.enterOuterAlt(localctx, 33) + self.state = 1296 + localctx.kw1 = self.match(SqlBaseParser.ALTER) + self.state = 1297 + localctx.kw2 = self.match(SqlBaseParser.TABLE) + self.state = 1298 + self.tableIdentifier() + self.state = 1299 + localctx.kw3 = self.match(SqlBaseParser.EXCHANGE) + self.state = 1300 + localctx.kw4 = self.match(SqlBaseParser.PARTITION) + pass + + elif la_ == 34: + self.enterOuterAlt(localctx, 34) + self.state = 1302 + localctx.kw1 = self.match(SqlBaseParser.ALTER) + self.state = 1303 + localctx.kw2 = self.match(SqlBaseParser.TABLE) + self.state = 1304 + self.tableIdentifier() + self.state = 1305 + localctx.kw3 = self.match(SqlBaseParser.ARCHIVE) + self.state = 1306 + localctx.kw4 = self.match(SqlBaseParser.PARTITION) + pass + + elif la_ == 35: + self.enterOuterAlt(localctx, 35) + self.state = 1308 + localctx.kw1 = self.match(SqlBaseParser.ALTER) + self.state = 1309 + localctx.kw2 = self.match(SqlBaseParser.TABLE) + self.state = 1310 + self.tableIdentifier() + self.state = 1311 + localctx.kw3 = self.match(SqlBaseParser.UNARCHIVE) + self.state = 1312 + localctx.kw4 = self.match(SqlBaseParser.PARTITION) + pass + + elif la_ == 36: + self.enterOuterAlt(localctx, 36) + self.state = 1314 + localctx.kw1 = self.match(SqlBaseParser.ALTER) + self.state = 1315 + localctx.kw2 = self.match(SqlBaseParser.TABLE) + self.state = 1316 + self.tableIdentifier() + self.state = 1317 + localctx.kw3 = self.match(SqlBaseParser.TOUCH) + pass + + elif la_ == 37: + self.enterOuterAlt(localctx, 37) + self.state = 1319 + localctx.kw1 = self.match(SqlBaseParser.ALTER) + self.state = 1320 + localctx.kw2 = self.match(SqlBaseParser.TABLE) + self.state = 1321 + self.tableIdentifier() + self.state = 1323 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==190: + self.state = 1322 + self.partitionSpec() + + + self.state = 1325 + localctx.kw3 = self.match(SqlBaseParser.COMPACT) + pass + + elif la_ == 38: + self.enterOuterAlt(localctx, 38) + self.state = 1327 + localctx.kw1 = self.match(SqlBaseParser.ALTER) + self.state = 1328 + localctx.kw2 = self.match(SqlBaseParser.TABLE) + self.state = 1329 + self.tableIdentifier() + self.state = 1331 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==190: + self.state = 1330 + self.partitionSpec() + + + self.state = 1333 + localctx.kw3 = self.match(SqlBaseParser.CONCATENATE) + pass + + elif la_ == 39: + self.enterOuterAlt(localctx, 39) + self.state = 1335 + localctx.kw1 = self.match(SqlBaseParser.ALTER) + self.state = 1336 + localctx.kw2 = self.match(SqlBaseParser.TABLE) + self.state = 1337 + self.tableIdentifier() + self.state = 1339 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==190: + self.state = 1338 + self.partitionSpec() + + + self.state = 1341 + localctx.kw3 = self.match(SqlBaseParser.SET) + self.state = 1342 + localctx.kw4 = self.match(SqlBaseParser.FILEFORMAT) + pass + + elif la_ == 40: + self.enterOuterAlt(localctx, 40) + self.state = 1344 + localctx.kw1 = self.match(SqlBaseParser.ALTER) + self.state = 1345 + localctx.kw2 = self.match(SqlBaseParser.TABLE) + self.state = 1346 + self.tableIdentifier() + self.state = 1348 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==190: + self.state = 1347 + self.partitionSpec() + + + self.state = 1350 + localctx.kw3 = self.match(SqlBaseParser.REPLACE) + self.state = 1351 + localctx.kw4 = self.match(SqlBaseParser.COLUMNS) + pass + + elif la_ == 41: + self.enterOuterAlt(localctx, 41) + self.state = 1353 + localctx.kw1 = self.match(SqlBaseParser.START) + self.state = 1354 + localctx.kw2 = self.match(SqlBaseParser.TRANSACTION) + pass + + elif la_ == 42: + self.enterOuterAlt(localctx, 42) + self.state = 1355 + localctx.kw1 = self.match(SqlBaseParser.COMMIT) + pass + + elif la_ == 43: + self.enterOuterAlt(localctx, 43) + self.state = 1356 + localctx.kw1 = self.match(SqlBaseParser.ROLLBACK) + pass + + elif la_ == 44: + self.enterOuterAlt(localctx, 44) + self.state = 1357 + localctx.kw1 = self.match(SqlBaseParser.DFS) + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class CreateTableHeaderContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def CREATE(self): + return self.getToken(SqlBaseParser.CREATE, 0) + + def TABLE(self): + return self.getToken(SqlBaseParser.TABLE, 0) + + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + + def TEMPORARY(self): + return self.getToken(SqlBaseParser.TEMPORARY, 0) + + def EXTERNAL(self): + return self.getToken(SqlBaseParser.EXTERNAL, 0) + + def IF(self): + return self.getToken(SqlBaseParser.IF, 0) + + def NOT(self): + return self.getToken(SqlBaseParser.NOT, 0) + + def EXISTS(self): + return self.getToken(SqlBaseParser.EXISTS, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_createTableHeader + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterCreateTableHeader" ): + listener.enterCreateTableHeader(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitCreateTableHeader" ): + listener.exitCreateTableHeader(self) + + + + + def createTableHeader(self): + + localctx = SqlBaseParser.CreateTableHeaderContext(self, self._ctx, self.state) + self.enterRule(localctx, 24, self.RULE_createTableHeader) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 1360 + self.match(SqlBaseParser.CREATE) + self.state = 1362 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==263: + self.state = 1361 + self.match(SqlBaseParser.TEMPORARY) + + + self.state = 1365 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==94: + self.state = 1364 + self.match(SqlBaseParser.EXTERNAL) + + + self.state = 1367 + self.match(SqlBaseParser.TABLE) + self.state = 1371 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,133,self._ctx) + if la_ == 1: + self.state = 1368 + self.match(SqlBaseParser.IF) + self.state = 1369 + self.match(SqlBaseParser.NOT) + self.state = 1370 + self.match(SqlBaseParser.EXISTS) + + + self.state = 1373 + self.multipartIdentifier() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class ReplaceTableHeaderContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def REPLACE(self): + return self.getToken(SqlBaseParser.REPLACE, 0) + + def TABLE(self): + return self.getToken(SqlBaseParser.TABLE, 0) + + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + + def CREATE(self): + return self.getToken(SqlBaseParser.CREATE, 0) + + def OR(self): + return self.getToken(SqlBaseParser.OR, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_replaceTableHeader + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterReplaceTableHeader" ): + listener.enterReplaceTableHeader(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitReplaceTableHeader" ): + listener.exitReplaceTableHeader(self) + + + + + def replaceTableHeader(self): + + localctx = SqlBaseParser.ReplaceTableHeaderContext(self, self._ctx, self.state) + self.enterRule(localctx, 26, self.RULE_replaceTableHeader) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 1377 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==53: + self.state = 1375 + self.match(SqlBaseParser.CREATE) + self.state = 1376 + self.match(SqlBaseParser.OR) + + + self.state = 1379 + self.match(SqlBaseParser.REPLACE) + self.state = 1380 + self.match(SqlBaseParser.TABLE) + self.state = 1381 + self.multipartIdentifier() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class BucketSpecContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def CLUSTERED(self): + return self.getToken(SqlBaseParser.CLUSTERED, 0) + + def BY(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.BY) + else: + return self.getToken(SqlBaseParser.BY, i) + + def identifierList(self): + return self.getTypedRuleContext(SqlBaseParser.IdentifierListContext,0) + + + def INTO(self): + return self.getToken(SqlBaseParser.INTO, 0) + + def INTEGER_VALUE(self): + return self.getToken(SqlBaseParser.INTEGER_VALUE, 0) + + def BUCKETS(self): + return self.getToken(SqlBaseParser.BUCKETS, 0) + + def SORTED(self): + return self.getToken(SqlBaseParser.SORTED, 0) + + def orderedIdentifierList(self): + return self.getTypedRuleContext(SqlBaseParser.OrderedIdentifierListContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_bucketSpec + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterBucketSpec" ): + listener.enterBucketSpec(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitBucketSpec" ): + listener.exitBucketSpec(self) + + + + + def bucketSpec(self): + + localctx = SqlBaseParser.BucketSpecContext(self, self._ctx, self.state) + self.enterRule(localctx, 28, self.RULE_bucketSpec) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 1383 + self.match(SqlBaseParser.CLUSTERED) + self.state = 1384 + self.match(SqlBaseParser.BY) + self.state = 1385 + self.identifierList() + self.state = 1389 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==246: + self.state = 1386 + self.match(SqlBaseParser.SORTED) + self.state = 1387 + self.match(SqlBaseParser.BY) + self.state = 1388 + self.orderedIdentifierList() + + + self.state = 1391 + self.match(SqlBaseParser.INTO) + self.state = 1392 + self.match(SqlBaseParser.INTEGER_VALUE) + self.state = 1393 + self.match(SqlBaseParser.BUCKETS) + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class SkewSpecContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def SKEWED(self): + return self.getToken(SqlBaseParser.SKEWED, 0) + + def BY(self): + return self.getToken(SqlBaseParser.BY, 0) + + def identifierList(self): + return self.getTypedRuleContext(SqlBaseParser.IdentifierListContext,0) + + + def ON(self): + return self.getToken(SqlBaseParser.ON, 0) + + def constantList(self): + return self.getTypedRuleContext(SqlBaseParser.ConstantListContext,0) + + + def nestedConstantList(self): + return self.getTypedRuleContext(SqlBaseParser.NestedConstantListContext,0) + + + def STORED(self): + return self.getToken(SqlBaseParser.STORED, 0) + + def AS(self): + return self.getToken(SqlBaseParser.AS, 0) + + def DIRECTORIES(self): + return self.getToken(SqlBaseParser.DIRECTORIES, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_skewSpec + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSkewSpec" ): + listener.enterSkewSpec(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSkewSpec" ): + listener.exitSkewSpec(self) + + + + + def skewSpec(self): + + localctx = SqlBaseParser.SkewSpecContext(self, self._ctx, self.state) + self.enterRule(localctx, 30, self.RULE_skewSpec) + try: + self.enterOuterAlt(localctx, 1) + self.state = 1395 + self.match(SqlBaseParser.SKEWED) + self.state = 1396 + self.match(SqlBaseParser.BY) + self.state = 1397 + self.identifierList() + self.state = 1398 + self.match(SqlBaseParser.ON) + self.state = 1401 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,136,self._ctx) + if la_ == 1: + self.state = 1399 + self.constantList() + pass + + elif la_ == 2: + self.state = 1400 + self.nestedConstantList() + pass + + + self.state = 1406 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,137,self._ctx) + if la_ == 1: + self.state = 1403 + self.match(SqlBaseParser.STORED) + self.state = 1404 + self.match(SqlBaseParser.AS) + self.state = 1405 + self.match(SqlBaseParser.DIRECTORIES) + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class LocationSpecContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def LOCATION(self): + return self.getToken(SqlBaseParser.LOCATION, 0) + + def stringLit(self): + return self.getTypedRuleContext(SqlBaseParser.StringLitContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_locationSpec + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterLocationSpec" ): + listener.enterLocationSpec(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitLocationSpec" ): + listener.exitLocationSpec(self) + + + + + def locationSpec(self): + + localctx = SqlBaseParser.LocationSpecContext(self, self._ctx, self.state) + self.enterRule(localctx, 32, self.RULE_locationSpec) + try: + self.enterOuterAlt(localctx, 1) + self.state = 1408 + self.match(SqlBaseParser.LOCATION) + self.state = 1409 + self.stringLit() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class CommentSpecContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def COMMENT(self): + return self.getToken(SqlBaseParser.COMMENT, 0) + + def stringLit(self): + return self.getTypedRuleContext(SqlBaseParser.StringLitContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_commentSpec + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterCommentSpec" ): + listener.enterCommentSpec(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitCommentSpec" ): + listener.exitCommentSpec(self) + + + + + def commentSpec(self): + + localctx = SqlBaseParser.CommentSpecContext(self, self._ctx, self.state) + self.enterRule(localctx, 34, self.RULE_commentSpec) + try: + self.enterOuterAlt(localctx, 1) + self.state = 1411 + self.match(SqlBaseParser.COMMENT) + self.state = 1412 + self.stringLit() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class QueryContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def queryTerm(self): + return self.getTypedRuleContext(SqlBaseParser.QueryTermContext,0) + + + def queryOrganization(self): + return self.getTypedRuleContext(SqlBaseParser.QueryOrganizationContext,0) + + + def ctes(self): + return self.getTypedRuleContext(SqlBaseParser.CtesContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_query + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterQuery" ): + listener.enterQuery(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitQuery" ): + listener.exitQuery(self) + + + + + def query(self): + + localctx = SqlBaseParser.QueryContext(self, self._ctx, self.state) + self.enterRule(localctx, 36, self.RULE_query) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 1415 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==303: + self.state = 1414 + self.ctes() + + + self.state = 1417 + self.queryTerm(0) + self.state = 1418 + self.queryOrganization() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class InsertIntoContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + + def getRuleIndex(self): + return SqlBaseParser.RULE_insertInto + + + def copyFrom(self, ctx:ParserRuleContext): + super().copyFrom(ctx) + + + + class InsertIntoReplaceWhereContext(InsertIntoContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.InsertIntoContext + super().__init__(parser) + self.copyFrom(ctx) + + def INSERT(self): + return self.getToken(SqlBaseParser.INSERT, 0) + def INTO(self): + return self.getToken(SqlBaseParser.INTO, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def REPLACE(self): + return self.getToken(SqlBaseParser.REPLACE, 0) + def whereClause(self): + return self.getTypedRuleContext(SqlBaseParser.WhereClauseContext,0) + + def TABLE(self): + return self.getToken(SqlBaseParser.TABLE, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterInsertIntoReplaceWhere" ): + listener.enterInsertIntoReplaceWhere(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitInsertIntoReplaceWhere" ): + listener.exitInsertIntoReplaceWhere(self) + + + class InsertOverwriteHiveDirContext(InsertIntoContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.InsertIntoContext + super().__init__(parser) + self.path = None # StringLitContext + self.copyFrom(ctx) + + def INSERT(self): + return self.getToken(SqlBaseParser.INSERT, 0) + def OVERWRITE(self): + return self.getToken(SqlBaseParser.OVERWRITE, 0) + def DIRECTORY(self): + return self.getToken(SqlBaseParser.DIRECTORY, 0) + def stringLit(self): + return self.getTypedRuleContext(SqlBaseParser.StringLitContext,0) + + def LOCAL(self): + return self.getToken(SqlBaseParser.LOCAL, 0) + def rowFormat(self): + return self.getTypedRuleContext(SqlBaseParser.RowFormatContext,0) + + def createFileFormat(self): + return self.getTypedRuleContext(SqlBaseParser.CreateFileFormatContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterInsertOverwriteHiveDir" ): + listener.enterInsertOverwriteHiveDir(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitInsertOverwriteHiveDir" ): + listener.exitInsertOverwriteHiveDir(self) + + + class InsertOverwriteDirContext(InsertIntoContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.InsertIntoContext + super().__init__(parser) + self.path = None # StringLitContext + self.options = None # PropertyListContext + self.copyFrom(ctx) + + def INSERT(self): + return self.getToken(SqlBaseParser.INSERT, 0) + def OVERWRITE(self): + return self.getToken(SqlBaseParser.OVERWRITE, 0) + def DIRECTORY(self): + return self.getToken(SqlBaseParser.DIRECTORY, 0) + def tableProvider(self): + return self.getTypedRuleContext(SqlBaseParser.TableProviderContext,0) + + def LOCAL(self): + return self.getToken(SqlBaseParser.LOCAL, 0) + def OPTIONS(self): + return self.getToken(SqlBaseParser.OPTIONS, 0) + def stringLit(self): + return self.getTypedRuleContext(SqlBaseParser.StringLitContext,0) + + def propertyList(self): + return self.getTypedRuleContext(SqlBaseParser.PropertyListContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterInsertOverwriteDir" ): + listener.enterInsertOverwriteDir(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitInsertOverwriteDir" ): + listener.exitInsertOverwriteDir(self) + + + class InsertOverwriteTableContext(InsertIntoContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.InsertIntoContext + super().__init__(parser) + self.copyFrom(ctx) + + def INSERT(self): + return self.getToken(SqlBaseParser.INSERT, 0) + def OVERWRITE(self): + return self.getToken(SqlBaseParser.OVERWRITE, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def TABLE(self): + return self.getToken(SqlBaseParser.TABLE, 0) + def partitionSpec(self): + return self.getTypedRuleContext(SqlBaseParser.PartitionSpecContext,0) + + def identifierList(self): + return self.getTypedRuleContext(SqlBaseParser.IdentifierListContext,0) + + def IF(self): + return self.getToken(SqlBaseParser.IF, 0) + def NOT(self): + return self.getToken(SqlBaseParser.NOT, 0) + def EXISTS(self): + return self.getToken(SqlBaseParser.EXISTS, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterInsertOverwriteTable" ): + listener.enterInsertOverwriteTable(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitInsertOverwriteTable" ): + listener.exitInsertOverwriteTable(self) + + + class InsertIntoTableContext(InsertIntoContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.InsertIntoContext + super().__init__(parser) + self.copyFrom(ctx) + + def INSERT(self): + return self.getToken(SqlBaseParser.INSERT, 0) + def INTO(self): + return self.getToken(SqlBaseParser.INTO, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def TABLE(self): + return self.getToken(SqlBaseParser.TABLE, 0) + def partitionSpec(self): + return self.getTypedRuleContext(SqlBaseParser.PartitionSpecContext,0) + + def IF(self): + return self.getToken(SqlBaseParser.IF, 0) + def NOT(self): + return self.getToken(SqlBaseParser.NOT, 0) + def EXISTS(self): + return self.getToken(SqlBaseParser.EXISTS, 0) + def identifierList(self): + return self.getTypedRuleContext(SqlBaseParser.IdentifierListContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterInsertIntoTable" ): + listener.enterInsertIntoTable(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitInsertIntoTable" ): + listener.exitInsertIntoTable(self) + + + + def insertInto(self): + + localctx = SqlBaseParser.InsertIntoContext(self, self._ctx, self.state) + self.enterRule(localctx, 38, self.RULE_insertInto) + self._la = 0 # Token type + try: + self.state = 1490 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,154,self._ctx) + if la_ == 1: + localctx = SqlBaseParser.InsertOverwriteTableContext(self, localctx) + self.enterOuterAlt(localctx, 1) + self.state = 1420 + self.match(SqlBaseParser.INSERT) + self.state = 1421 + self.match(SqlBaseParser.OVERWRITE) + self.state = 1423 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,139,self._ctx) + if la_ == 1: + self.state = 1422 + self.match(SqlBaseParser.TABLE) + + + self.state = 1425 + self.multipartIdentifier() + self.state = 1432 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==190: + self.state = 1426 + self.partitionSpec() + self.state = 1430 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==119: + self.state = 1427 + self.match(SqlBaseParser.IF) + self.state = 1428 + self.match(SqlBaseParser.NOT) + self.state = 1429 + self.match(SqlBaseParser.EXISTS) + + + + + self.state = 1435 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,142,self._ctx) + if la_ == 1: + self.state = 1434 + self.identifierList() + + + pass + + elif la_ == 2: + localctx = SqlBaseParser.InsertIntoTableContext(self, localctx) + self.enterOuterAlt(localctx, 2) + self.state = 1437 + self.match(SqlBaseParser.INSERT) + self.state = 1438 + self.match(SqlBaseParser.INTO) + self.state = 1440 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,143,self._ctx) + if la_ == 1: + self.state = 1439 + self.match(SqlBaseParser.TABLE) + + + self.state = 1442 + self.multipartIdentifier() + self.state = 1444 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==190: + self.state = 1443 + self.partitionSpec() + + + self.state = 1449 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==119: + self.state = 1446 + self.match(SqlBaseParser.IF) + self.state = 1447 + self.match(SqlBaseParser.NOT) + self.state = 1448 + self.match(SqlBaseParser.EXISTS) + + + self.state = 1452 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,146,self._ctx) + if la_ == 1: + self.state = 1451 + self.identifierList() + + + pass + + elif la_ == 3: + localctx = SqlBaseParser.InsertIntoReplaceWhereContext(self, localctx) + self.enterOuterAlt(localctx, 3) + self.state = 1454 + self.match(SqlBaseParser.INSERT) + self.state = 1455 + self.match(SqlBaseParser.INTO) + self.state = 1457 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,147,self._ctx) + if la_ == 1: + self.state = 1456 + self.match(SqlBaseParser.TABLE) + + + self.state = 1459 + self.multipartIdentifier() + self.state = 1460 + self.match(SqlBaseParser.REPLACE) + self.state = 1461 + self.whereClause() + pass + + elif la_ == 4: + localctx = SqlBaseParser.InsertOverwriteHiveDirContext(self, localctx) + self.enterOuterAlt(localctx, 4) + self.state = 1463 + self.match(SqlBaseParser.INSERT) + self.state = 1464 + self.match(SqlBaseParser.OVERWRITE) + self.state = 1466 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==148: + self.state = 1465 + self.match(SqlBaseParser.LOCAL) + + + self.state = 1468 + self.match(SqlBaseParser.DIRECTORY) + self.state = 1469 + localctx.path = self.stringLit() + self.state = 1471 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==227: + self.state = 1470 + self.rowFormat() + + + self.state = 1474 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==250: + self.state = 1473 + self.createFileFormat() + + + pass + + elif la_ == 5: + localctx = SqlBaseParser.InsertOverwriteDirContext(self, localctx) + self.enterOuterAlt(localctx, 5) + self.state = 1476 + self.match(SqlBaseParser.INSERT) + self.state = 1477 + self.match(SqlBaseParser.OVERWRITE) + self.state = 1479 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==148: + self.state = 1478 + self.match(SqlBaseParser.LOCAL) + + + self.state = 1481 + self.match(SqlBaseParser.DIRECTORY) + self.state = 1483 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==330 or _la==331: + self.state = 1482 + localctx.path = self.stringLit() + + + self.state = 1485 + self.tableProvider() + self.state = 1488 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==180: + self.state = 1486 + self.match(SqlBaseParser.OPTIONS) + self.state = 1487 + localctx.options = self.propertyList() + + + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class PartitionSpecLocationContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def partitionSpec(self): + return self.getTypedRuleContext(SqlBaseParser.PartitionSpecContext,0) + + + def locationSpec(self): + return self.getTypedRuleContext(SqlBaseParser.LocationSpecContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_partitionSpecLocation + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterPartitionSpecLocation" ): + listener.enterPartitionSpecLocation(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitPartitionSpecLocation" ): + listener.exitPartitionSpecLocation(self) + + + + + def partitionSpecLocation(self): + + localctx = SqlBaseParser.PartitionSpecLocationContext(self, self._ctx, self.state) + self.enterRule(localctx, 40, self.RULE_partitionSpecLocation) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 1492 + self.partitionSpec() + self.state = 1494 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==149: + self.state = 1493 + self.locationSpec() + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class PartitionSpecContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def PARTITION(self): + return self.getToken(SqlBaseParser.PARTITION, 0) + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + + def partitionVal(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.PartitionValContext) + else: + return self.getTypedRuleContext(SqlBaseParser.PartitionValContext,i) + + + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def getRuleIndex(self): + return SqlBaseParser.RULE_partitionSpec + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterPartitionSpec" ): + listener.enterPartitionSpec(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitPartitionSpec" ): + listener.exitPartitionSpec(self) + + + + + def partitionSpec(self): + + localctx = SqlBaseParser.PartitionSpecContext(self, self._ctx, self.state) + self.enterRule(localctx, 42, self.RULE_partitionSpec) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 1496 + self.match(SqlBaseParser.PARTITION) + self.state = 1497 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 1498 + self.partitionVal() + self.state = 1503 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 1499 + self.match(SqlBaseParser.COMMA) + self.state = 1500 + self.partitionVal() + self.state = 1505 + self._errHandler.sync(self) + _la = self._input.LA(1) + + self.state = 1506 + self.match(SqlBaseParser.RIGHT_PAREN) + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class PartitionValContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def identifier(self): + return self.getTypedRuleContext(SqlBaseParser.IdentifierContext,0) + + + def EQ(self): + return self.getToken(SqlBaseParser.EQ, 0) + + def constant(self): + return self.getTypedRuleContext(SqlBaseParser.ConstantContext,0) + + + def DEFAULT(self): + return self.getToken(SqlBaseParser.DEFAULT, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_partitionVal + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterPartitionVal" ): + listener.enterPartitionVal(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitPartitionVal" ): + listener.exitPartitionVal(self) + + + + + def partitionVal(self): + + localctx = SqlBaseParser.PartitionValContext(self, self._ctx, self.state) + self.enterRule(localctx, 44, self.RULE_partitionVal) + self._la = 0 # Token type + try: + self.state = 1517 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,158,self._ctx) + if la_ == 1: + self.enterOuterAlt(localctx, 1) + self.state = 1508 + self.identifier() + self.state = 1511 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==308: + self.state = 1509 + self.match(SqlBaseParser.EQ) + self.state = 1510 + self.constant() + + + pass + + elif la_ == 2: + self.enterOuterAlt(localctx, 2) + self.state = 1513 + self.identifier() + self.state = 1514 + self.match(SqlBaseParser.EQ) + self.state = 1515 + self.match(SqlBaseParser.DEFAULT) + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class NamespaceContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def NAMESPACE(self): + return self.getToken(SqlBaseParser.NAMESPACE, 0) + + def DATABASE(self): + return self.getToken(SqlBaseParser.DATABASE, 0) + + def SCHEMA(self): + return self.getToken(SqlBaseParser.SCHEMA, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_namespace + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterNamespace" ): + listener.enterNamespace(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitNamespace" ): + listener.exitNamespace(self) + + + + + def namespace(self): + + localctx = SqlBaseParser.NamespaceContext(self, self._ctx, self.state) + self.enterRule(localctx, 46, self.RULE_namespace) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 1519 + _la = self._input.LA(1) + if not(_la==65 or _la==166 or _la==231): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class NamespacesContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def NAMESPACES(self): + return self.getToken(SqlBaseParser.NAMESPACES, 0) + + def DATABASES(self): + return self.getToken(SqlBaseParser.DATABASES, 0) + + def SCHEMAS(self): + return self.getToken(SqlBaseParser.SCHEMAS, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_namespaces + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterNamespaces" ): + listener.enterNamespaces(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitNamespaces" ): + listener.exitNamespaces(self) + + + + + def namespaces(self): + + localctx = SqlBaseParser.NamespacesContext(self, self._ctx, self.state) + self.enterRule(localctx, 48, self.RULE_namespaces) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 1521 + _la = self._input.LA(1) + if not(_la==66 or _la==167 or _la==232): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class DescribeFuncNameContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def qualifiedName(self): + return self.getTypedRuleContext(SqlBaseParser.QualifiedNameContext,0) + + + def stringLit(self): + return self.getTypedRuleContext(SqlBaseParser.StringLitContext,0) + + + def comparisonOperator(self): + return self.getTypedRuleContext(SqlBaseParser.ComparisonOperatorContext,0) + + + def arithmeticOperator(self): + return self.getTypedRuleContext(SqlBaseParser.ArithmeticOperatorContext,0) + + + def predicateOperator(self): + return self.getTypedRuleContext(SqlBaseParser.PredicateOperatorContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_describeFuncName + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterDescribeFuncName" ): + listener.enterDescribeFuncName(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitDescribeFuncName" ): + listener.exitDescribeFuncName(self) + + + + + def describeFuncName(self): + + localctx = SqlBaseParser.DescribeFuncNameContext(self, self._ctx, self.state) + self.enterRule(localctx, 50, self.RULE_describeFuncName) + try: + self.state = 1528 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,159,self._ctx) + if la_ == 1: + self.enterOuterAlt(localctx, 1) + self.state = 1523 + self.qualifiedName() + pass + + elif la_ == 2: + self.enterOuterAlt(localctx, 2) + self.state = 1524 + self.stringLit() + pass + + elif la_ == 3: + self.enterOuterAlt(localctx, 3) + self.state = 1525 + self.comparisonOperator() + pass + + elif la_ == 4: + self.enterOuterAlt(localctx, 4) + self.state = 1526 + self.arithmeticOperator() + pass + + elif la_ == 5: + self.enterOuterAlt(localctx, 5) + self.state = 1527 + self.predicateOperator() + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class DescribeColNameContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self._identifier = None # IdentifierContext + self.nameParts = list() # of IdentifierContexts + + def identifier(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.IdentifierContext) + else: + return self.getTypedRuleContext(SqlBaseParser.IdentifierContext,i) + + + def DOT(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.DOT) + else: + return self.getToken(SqlBaseParser.DOT, i) + + def getRuleIndex(self): + return SqlBaseParser.RULE_describeColName + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterDescribeColName" ): + listener.enterDescribeColName(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitDescribeColName" ): + listener.exitDescribeColName(self) + + + + + def describeColName(self): + + localctx = SqlBaseParser.DescribeColNameContext(self, self._ctx, self.state) + self.enterRule(localctx, 52, self.RULE_describeColName) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 1530 + localctx._identifier = self.identifier() + localctx.nameParts.append(localctx._identifier) + self.state = 1535 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==5: + self.state = 1531 + self.match(SqlBaseParser.DOT) + self.state = 1532 + localctx._identifier = self.identifier() + localctx.nameParts.append(localctx._identifier) + self.state = 1537 + self._errHandler.sync(self) + _la = self._input.LA(1) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class CtesContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def WITH(self): + return self.getToken(SqlBaseParser.WITH, 0) + + def namedQuery(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.NamedQueryContext) + else: + return self.getTypedRuleContext(SqlBaseParser.NamedQueryContext,i) + + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def getRuleIndex(self): + return SqlBaseParser.RULE_ctes + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterCtes" ): + listener.enterCtes(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitCtes" ): + listener.exitCtes(self) + + + + + def ctes(self): + + localctx = SqlBaseParser.CtesContext(self, self._ctx, self.state) + self.enterRule(localctx, 54, self.RULE_ctes) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 1538 + self.match(SqlBaseParser.WITH) + self.state = 1539 + self.namedQuery() + self.state = 1544 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 1540 + self.match(SqlBaseParser.COMMA) + self.state = 1541 + self.namedQuery() + self.state = 1546 + self._errHandler.sync(self) + _la = self._input.LA(1) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class NamedQueryContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self.name = None # ErrorCapturingIdentifierContext + self.columnAliases = None # IdentifierListContext + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + + def query(self): + return self.getTypedRuleContext(SqlBaseParser.QueryContext,0) + + + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + + def errorCapturingIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.ErrorCapturingIdentifierContext,0) + + + def AS(self): + return self.getToken(SqlBaseParser.AS, 0) + + def identifierList(self): + return self.getTypedRuleContext(SqlBaseParser.IdentifierListContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_namedQuery + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterNamedQuery" ): + listener.enterNamedQuery(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitNamedQuery" ): + listener.exitNamedQuery(self) + + + + + def namedQuery(self): + + localctx = SqlBaseParser.NamedQueryContext(self, self._ctx, self.state) + self.enterRule(localctx, 56, self.RULE_namedQuery) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 1547 + localctx.name = self.errorCapturingIdentifier() + self.state = 1549 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,162,self._ctx) + if la_ == 1: + self.state = 1548 + localctx.columnAliases = self.identifierList() + + + self.state = 1552 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==20: + self.state = 1551 + self.match(SqlBaseParser.AS) + + + self.state = 1554 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 1555 + self.query() + self.state = 1556 + self.match(SqlBaseParser.RIGHT_PAREN) + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class TableProviderContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def USING(self): + return self.getToken(SqlBaseParser.USING, 0) + + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_tableProvider + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterTableProvider" ): + listener.enterTableProvider(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitTableProvider" ): + listener.exitTableProvider(self) + + + + + def tableProvider(self): + + localctx = SqlBaseParser.TableProviderContext(self, self._ctx, self.state) + self.enterRule(localctx, 58, self.RULE_tableProvider) + try: + self.enterOuterAlt(localctx, 1) + self.state = 1558 + self.match(SqlBaseParser.USING) + self.state = 1559 + self.multipartIdentifier() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class CreateTableClausesContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self.options = None # PropertyListContext + self.partitioning = None # PartitionFieldListContext + self.tableProps = None # PropertyListContext + + def skewSpec(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.SkewSpecContext) + else: + return self.getTypedRuleContext(SqlBaseParser.SkewSpecContext,i) + + + def bucketSpec(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.BucketSpecContext) + else: + return self.getTypedRuleContext(SqlBaseParser.BucketSpecContext,i) + + + def rowFormat(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.RowFormatContext) + else: + return self.getTypedRuleContext(SqlBaseParser.RowFormatContext,i) + + + def createFileFormat(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.CreateFileFormatContext) + else: + return self.getTypedRuleContext(SqlBaseParser.CreateFileFormatContext,i) + + + def locationSpec(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.LocationSpecContext) + else: + return self.getTypedRuleContext(SqlBaseParser.LocationSpecContext,i) + + + def commentSpec(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.CommentSpecContext) + else: + return self.getTypedRuleContext(SqlBaseParser.CommentSpecContext,i) + + + def OPTIONS(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.OPTIONS) + else: + return self.getToken(SqlBaseParser.OPTIONS, i) + + def PARTITIONED(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.PARTITIONED) + else: + return self.getToken(SqlBaseParser.PARTITIONED, i) + + def BY(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.BY) + else: + return self.getToken(SqlBaseParser.BY, i) + + def TBLPROPERTIES(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.TBLPROPERTIES) + else: + return self.getToken(SqlBaseParser.TBLPROPERTIES, i) + + def propertyList(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.PropertyListContext) + else: + return self.getTypedRuleContext(SqlBaseParser.PropertyListContext,i) + + + def partitionFieldList(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.PartitionFieldListContext) + else: + return self.getTypedRuleContext(SqlBaseParser.PartitionFieldListContext,i) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_createTableClauses + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterCreateTableClauses" ): + listener.enterCreateTableClauses(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitCreateTableClauses" ): + listener.exitCreateTableClauses(self) + + + + + def createTableClauses(self): + + localctx = SqlBaseParser.CreateTableClausesContext(self, self._ctx, self.state) + self.enterRule(localctx, 60, self.RULE_createTableClauses) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 1576 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==39 or _la==45 or ((((_la - 149)) & ~0x3f) == 0 and ((1 << (_la - 149)) & 4400193994753) != 0) or ((((_la - 227)) & ~0x3f) == 0 and ((1 << (_la - 227)) & 34368192513) != 0): + self.state = 1574 + self._errHandler.sync(self) + token = self._input.LA(1) + if token in [180]: + self.state = 1561 + self.match(SqlBaseParser.OPTIONS) + self.state = 1562 + localctx.options = self.propertyList() + pass + elif token in [191]: + self.state = 1563 + self.match(SqlBaseParser.PARTITIONED) + self.state = 1564 + self.match(SqlBaseParser.BY) + self.state = 1565 + localctx.partitioning = self.partitionFieldList() + pass + elif token in [243]: + self.state = 1566 + self.skewSpec() + pass + elif token in [39]: + self.state = 1567 + self.bucketSpec() + pass + elif token in [227]: + self.state = 1568 + self.rowFormat() + pass + elif token in [250]: + self.state = 1569 + self.createFileFormat() + pass + elif token in [149]: + self.state = 1570 + self.locationSpec() + pass + elif token in [45]: + self.state = 1571 + self.commentSpec() + pass + elif token in [262]: + self.state = 1572 + self.match(SqlBaseParser.TBLPROPERTIES) + self.state = 1573 + localctx.tableProps = self.propertyList() + pass + else: + raise NoViableAltException(self) + + self.state = 1578 + self._errHandler.sync(self) + _la = self._input.LA(1) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class PropertyListContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + + def property_(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.PropertyContext) + else: + return self.getTypedRuleContext(SqlBaseParser.PropertyContext,i) + + + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def getRuleIndex(self): + return SqlBaseParser.RULE_propertyList + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterPropertyList" ): + listener.enterPropertyList(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitPropertyList" ): + listener.exitPropertyList(self) + + + + + def propertyList(self): + + localctx = SqlBaseParser.PropertyListContext(self, self._ctx, self.state) + self.enterRule(localctx, 62, self.RULE_propertyList) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 1579 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 1580 + self.property_() + self.state = 1585 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 1581 + self.match(SqlBaseParser.COMMA) + self.state = 1582 + self.property_() + self.state = 1587 + self._errHandler.sync(self) + _la = self._input.LA(1) + + self.state = 1588 + self.match(SqlBaseParser.RIGHT_PAREN) + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class PropertyContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self.key = None # PropertyKeyContext + self.value = None # PropertyValueContext + + def propertyKey(self): + return self.getTypedRuleContext(SqlBaseParser.PropertyKeyContext,0) + + + def propertyValue(self): + return self.getTypedRuleContext(SqlBaseParser.PropertyValueContext,0) + + + def EQ(self): + return self.getToken(SqlBaseParser.EQ, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_property + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterProperty" ): + listener.enterProperty(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitProperty" ): + listener.exitProperty(self) + + + + + def property_(self): + + localctx = SqlBaseParser.PropertyContext(self, self._ctx, self.state) + self.enterRule(localctx, 64, self.RULE_property) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 1590 + localctx.key = self.propertyKey() + self.state = 1595 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==96 or ((((_la - 277)) & ~0x3f) == 0 and ((1 << (_la - 277)) & 1468173480670265345) != 0): + self.state = 1592 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==308: + self.state = 1591 + self.match(SqlBaseParser.EQ) + + + self.state = 1594 + localctx.value = self.propertyValue() + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class PropertyKeyContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def identifier(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.IdentifierContext) + else: + return self.getTypedRuleContext(SqlBaseParser.IdentifierContext,i) + + + def DOT(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.DOT) + else: + return self.getToken(SqlBaseParser.DOT, i) + + def stringLit(self): + return self.getTypedRuleContext(SqlBaseParser.StringLitContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_propertyKey + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterPropertyKey" ): + listener.enterPropertyKey(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitPropertyKey" ): + listener.exitPropertyKey(self) + + + + + def propertyKey(self): + + localctx = SqlBaseParser.PropertyKeyContext(self, self._ctx, self.state) + self.enterRule(localctx, 66, self.RULE_propertyKey) + self._la = 0 # Token type + try: + self.state = 1606 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,170,self._ctx) + if la_ == 1: + self.enterOuterAlt(localctx, 1) + self.state = 1597 + self.identifier() + self.state = 1602 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==5: + self.state = 1598 + self.match(SqlBaseParser.DOT) + self.state = 1599 + self.identifier() + self.state = 1604 + self._errHandler.sync(self) + _la = self._input.LA(1) + + pass + + elif la_ == 2: + self.enterOuterAlt(localctx, 2) + self.state = 1605 + self.stringLit() + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class PropertyValueContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def INTEGER_VALUE(self): + return self.getToken(SqlBaseParser.INTEGER_VALUE, 0) + + def DECIMAL_VALUE(self): + return self.getToken(SqlBaseParser.DECIMAL_VALUE, 0) + + def booleanValue(self): + return self.getTypedRuleContext(SqlBaseParser.BooleanValueContext,0) + + + def stringLit(self): + return self.getTypedRuleContext(SqlBaseParser.StringLitContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_propertyValue + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterPropertyValue" ): + listener.enterPropertyValue(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitPropertyValue" ): + listener.exitPropertyValue(self) + + + + + def propertyValue(self): + + localctx = SqlBaseParser.PropertyValueContext(self, self._ctx, self.state) + self.enterRule(localctx, 68, self.RULE_propertyValue) + try: + self.state = 1612 + self._errHandler.sync(self) + token = self._input.LA(1) + if token in [335]: + self.enterOuterAlt(localctx, 1) + self.state = 1608 + self.match(SqlBaseParser.INTEGER_VALUE) + pass + elif token in [337]: + self.enterOuterAlt(localctx, 2) + self.state = 1609 + self.match(SqlBaseParser.DECIMAL_VALUE) + pass + elif token in [96, 277]: + self.enterOuterAlt(localctx, 3) + self.state = 1610 + self.booleanValue() + pass + elif token in [330, 331]: + self.enterOuterAlt(localctx, 4) + self.state = 1611 + self.stringLit() + pass + else: + raise NoViableAltException(self) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class ConstantListContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + + def constant(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.ConstantContext) + else: + return self.getTypedRuleContext(SqlBaseParser.ConstantContext,i) + + + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def getRuleIndex(self): + return SqlBaseParser.RULE_constantList + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterConstantList" ): + listener.enterConstantList(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitConstantList" ): + listener.exitConstantList(self) + + + + + def constantList(self): + + localctx = SqlBaseParser.ConstantListContext(self, self._ctx, self.state) + self.enterRule(localctx, 70, self.RULE_constantList) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 1614 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 1615 + self.constant() + self.state = 1620 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 1616 + self.match(SqlBaseParser.COMMA) + self.state = 1617 + self.constant() + self.state = 1622 + self._errHandler.sync(self) + _la = self._input.LA(1) + + self.state = 1623 + self.match(SqlBaseParser.RIGHT_PAREN) + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class NestedConstantListContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + + def constantList(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.ConstantListContext) + else: + return self.getTypedRuleContext(SqlBaseParser.ConstantListContext,i) + + + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def getRuleIndex(self): + return SqlBaseParser.RULE_nestedConstantList + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterNestedConstantList" ): + listener.enterNestedConstantList(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitNestedConstantList" ): + listener.exitNestedConstantList(self) + + + + + def nestedConstantList(self): + + localctx = SqlBaseParser.NestedConstantListContext(self, self._ctx, self.state) + self.enterRule(localctx, 72, self.RULE_nestedConstantList) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 1625 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 1626 + self.constantList() + self.state = 1631 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 1627 + self.match(SqlBaseParser.COMMA) + self.state = 1628 + self.constantList() + self.state = 1633 + self._errHandler.sync(self) + _la = self._input.LA(1) + + self.state = 1634 + self.match(SqlBaseParser.RIGHT_PAREN) + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class CreateFileFormatContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def STORED(self): + return self.getToken(SqlBaseParser.STORED, 0) + + def AS(self): + return self.getToken(SqlBaseParser.AS, 0) + + def fileFormat(self): + return self.getTypedRuleContext(SqlBaseParser.FileFormatContext,0) + + + def BY(self): + return self.getToken(SqlBaseParser.BY, 0) + + def storageHandler(self): + return self.getTypedRuleContext(SqlBaseParser.StorageHandlerContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_createFileFormat + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterCreateFileFormat" ): + listener.enterCreateFileFormat(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitCreateFileFormat" ): + listener.exitCreateFileFormat(self) + + + + + def createFileFormat(self): + + localctx = SqlBaseParser.CreateFileFormatContext(self, self._ctx, self.state) + self.enterRule(localctx, 74, self.RULE_createFileFormat) + try: + self.state = 1642 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,174,self._ctx) + if la_ == 1: + self.enterOuterAlt(localctx, 1) + self.state = 1636 + self.match(SqlBaseParser.STORED) + self.state = 1637 + self.match(SqlBaseParser.AS) + self.state = 1638 + self.fileFormat() + pass + + elif la_ == 2: + self.enterOuterAlt(localctx, 2) + self.state = 1639 + self.match(SqlBaseParser.STORED) + self.state = 1640 + self.match(SqlBaseParser.BY) + self.state = 1641 + self.storageHandler() + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class FileFormatContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + + def getRuleIndex(self): + return SqlBaseParser.RULE_fileFormat + + + def copyFrom(self, ctx:ParserRuleContext): + super().copyFrom(ctx) + + + + class TableFileFormatContext(FileFormatContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.FileFormatContext + super().__init__(parser) + self.inFmt = None # StringLitContext + self.outFmt = None # StringLitContext + self.copyFrom(ctx) + + def INPUTFORMAT(self): + return self.getToken(SqlBaseParser.INPUTFORMAT, 0) + def OUTPUTFORMAT(self): + return self.getToken(SqlBaseParser.OUTPUTFORMAT, 0) + def stringLit(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.StringLitContext) + else: + return self.getTypedRuleContext(SqlBaseParser.StringLitContext,i) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterTableFileFormat" ): + listener.enterTableFileFormat(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitTableFileFormat" ): + listener.exitTableFileFormat(self) + + + class GenericFileFormatContext(FileFormatContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.FileFormatContext + super().__init__(parser) + self.copyFrom(ctx) + + def identifier(self): + return self.getTypedRuleContext(SqlBaseParser.IdentifierContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterGenericFileFormat" ): + listener.enterGenericFileFormat(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitGenericFileFormat" ): + listener.exitGenericFileFormat(self) + + + + def fileFormat(self): + + localctx = SqlBaseParser.FileFormatContext(self, self._ctx, self.state) + self.enterRule(localctx, 76, self.RULE_fileFormat) + try: + self.state = 1650 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,175,self._ctx) + if la_ == 1: + localctx = SqlBaseParser.TableFileFormatContext(self, localctx) + self.enterOuterAlt(localctx, 1) + self.state = 1644 + self.match(SqlBaseParser.INPUTFORMAT) + self.state = 1645 + localctx.inFmt = self.stringLit() + self.state = 1646 + self.match(SqlBaseParser.OUTPUTFORMAT) + self.state = 1647 + localctx.outFmt = self.stringLit() + pass + + elif la_ == 2: + localctx = SqlBaseParser.GenericFileFormatContext(self, localctx) + self.enterOuterAlt(localctx, 2) + self.state = 1649 + self.identifier() + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class StorageHandlerContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def stringLit(self): + return self.getTypedRuleContext(SqlBaseParser.StringLitContext,0) + + + def WITH(self): + return self.getToken(SqlBaseParser.WITH, 0) + + def SERDEPROPERTIES(self): + return self.getToken(SqlBaseParser.SERDEPROPERTIES, 0) + + def propertyList(self): + return self.getTypedRuleContext(SqlBaseParser.PropertyListContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_storageHandler + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterStorageHandler" ): + listener.enterStorageHandler(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitStorageHandler" ): + listener.exitStorageHandler(self) + + + + + def storageHandler(self): + + localctx = SqlBaseParser.StorageHandlerContext(self, self._ctx, self.state) + self.enterRule(localctx, 78, self.RULE_storageHandler) + try: + self.enterOuterAlt(localctx, 1) + self.state = 1652 + self.stringLit() + self.state = 1656 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,176,self._ctx) + if la_ == 1: + self.state = 1653 + self.match(SqlBaseParser.WITH) + self.state = 1654 + self.match(SqlBaseParser.SERDEPROPERTIES) + self.state = 1655 + self.propertyList() + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class ResourceContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def identifier(self): + return self.getTypedRuleContext(SqlBaseParser.IdentifierContext,0) + + + def stringLit(self): + return self.getTypedRuleContext(SqlBaseParser.StringLitContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_resource + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterResource" ): + listener.enterResource(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitResource" ): + listener.exitResource(self) + + + + + def resource(self): + + localctx = SqlBaseParser.ResourceContext(self, self._ctx, self.state) + self.enterRule(localctx, 80, self.RULE_resource) + try: + self.enterOuterAlt(localctx, 1) + self.state = 1658 + self.identifier() + self.state = 1659 + self.stringLit() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class DmlStatementNoWithContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + + def getRuleIndex(self): + return SqlBaseParser.RULE_dmlStatementNoWith + + + def copyFrom(self, ctx:ParserRuleContext): + super().copyFrom(ctx) + + + + class DeleteFromTableContext(DmlStatementNoWithContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.DmlStatementNoWithContext + super().__init__(parser) + self.copyFrom(ctx) + + def DELETE(self): + return self.getToken(SqlBaseParser.DELETE, 0) + def FROM(self): + return self.getToken(SqlBaseParser.FROM, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def tableAlias(self): + return self.getTypedRuleContext(SqlBaseParser.TableAliasContext,0) + + def whereClause(self): + return self.getTypedRuleContext(SqlBaseParser.WhereClauseContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterDeleteFromTable" ): + listener.enterDeleteFromTable(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitDeleteFromTable" ): + listener.exitDeleteFromTable(self) + + + class SingleInsertQueryContext(DmlStatementNoWithContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.DmlStatementNoWithContext + super().__init__(parser) + self.copyFrom(ctx) + + def insertInto(self): + return self.getTypedRuleContext(SqlBaseParser.InsertIntoContext,0) + + def query(self): + return self.getTypedRuleContext(SqlBaseParser.QueryContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSingleInsertQuery" ): + listener.enterSingleInsertQuery(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSingleInsertQuery" ): + listener.exitSingleInsertQuery(self) + + + class MultiInsertQueryContext(DmlStatementNoWithContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.DmlStatementNoWithContext + super().__init__(parser) + self.copyFrom(ctx) + + def fromClause(self): + return self.getTypedRuleContext(SqlBaseParser.FromClauseContext,0) + + def multiInsertQueryBody(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.MultiInsertQueryBodyContext) + else: + return self.getTypedRuleContext(SqlBaseParser.MultiInsertQueryBodyContext,i) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterMultiInsertQuery" ): + listener.enterMultiInsertQuery(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitMultiInsertQuery" ): + listener.exitMultiInsertQuery(self) + + + class UpdateTableContext(DmlStatementNoWithContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.DmlStatementNoWithContext + super().__init__(parser) + self.copyFrom(ctx) + + def UPDATE(self): + return self.getToken(SqlBaseParser.UPDATE, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def tableAlias(self): + return self.getTypedRuleContext(SqlBaseParser.TableAliasContext,0) + + def setClause(self): + return self.getTypedRuleContext(SqlBaseParser.SetClauseContext,0) + + def whereClause(self): + return self.getTypedRuleContext(SqlBaseParser.WhereClauseContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterUpdateTable" ): + listener.enterUpdateTable(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitUpdateTable" ): + listener.exitUpdateTable(self) + + + class MergeIntoTableContext(DmlStatementNoWithContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.DmlStatementNoWithContext + super().__init__(parser) + self.target = None # MultipartIdentifierContext + self.targetAlias = None # TableAliasContext + self.source = None # MultipartIdentifierContext + self.sourceQuery = None # QueryContext + self.sourceAlias = None # TableAliasContext + self.mergeCondition = None # BooleanExpressionContext + self.copyFrom(ctx) + + def MERGE(self): + return self.getToken(SqlBaseParser.MERGE, 0) + def INTO(self): + return self.getToken(SqlBaseParser.INTO, 0) + def USING(self): + return self.getToken(SqlBaseParser.USING, 0) + def ON(self): + return self.getToken(SqlBaseParser.ON, 0) + def multipartIdentifier(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.MultipartIdentifierContext) + else: + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,i) + + def tableAlias(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.TableAliasContext) + else: + return self.getTypedRuleContext(SqlBaseParser.TableAliasContext,i) + + def booleanExpression(self): + return self.getTypedRuleContext(SqlBaseParser.BooleanExpressionContext,0) + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + def query(self): + return self.getTypedRuleContext(SqlBaseParser.QueryContext,0) + + def matchedClause(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.MatchedClauseContext) + else: + return self.getTypedRuleContext(SqlBaseParser.MatchedClauseContext,i) + + def notMatchedClause(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.NotMatchedClauseContext) + else: + return self.getTypedRuleContext(SqlBaseParser.NotMatchedClauseContext,i) + + def notMatchedBySourceClause(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.NotMatchedBySourceClauseContext) + else: + return self.getTypedRuleContext(SqlBaseParser.NotMatchedBySourceClauseContext,i) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterMergeIntoTable" ): + listener.enterMergeIntoTable(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitMergeIntoTable" ): + listener.exitMergeIntoTable(self) + + + + def dmlStatementNoWith(self): + + localctx = SqlBaseParser.DmlStatementNoWithContext(self, self._ctx, self.state) + self.enterRule(localctx, 82, self.RULE_dmlStatementNoWith) + self._la = 0 # Token type + try: + self.state = 1717 + self._errHandler.sync(self) + token = self._input.LA(1) + if token in [129]: + localctx = SqlBaseParser.SingleInsertQueryContext(self, localctx) + self.enterOuterAlt(localctx, 1) + self.state = 1661 + self.insertInto() + self.state = 1662 + self.query() + pass + elif token in [107]: + localctx = SqlBaseParser.MultiInsertQueryContext(self, localctx) + self.enterOuterAlt(localctx, 2) + self.state = 1664 + self.fromClause() + self.state = 1666 + self._errHandler.sync(self) + _la = self._input.LA(1) + while True: + self.state = 1665 + self.multiInsertQueryBody() + self.state = 1668 + self._errHandler.sync(self) + _la = self._input.LA(1) + if not (_la==129): + break + + pass + elif token in [72]: + localctx = SqlBaseParser.DeleteFromTableContext(self, localctx) + self.enterOuterAlt(localctx, 3) + self.state = 1670 + self.match(SqlBaseParser.DELETE) + self.state = 1671 + self.match(SqlBaseParser.FROM) + self.state = 1672 + self.multipartIdentifier() + self.state = 1673 + self.tableAlias() + self.state = 1675 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==301: + self.state = 1674 + self.whereClause() + + + pass + elif token in [290]: + localctx = SqlBaseParser.UpdateTableContext(self, localctx) + self.enterOuterAlt(localctx, 4) + self.state = 1677 + self.match(SqlBaseParser.UPDATE) + self.state = 1678 + self.multipartIdentifier() + self.state = 1679 + self.tableAlias() + self.state = 1680 + self.setClause() + self.state = 1682 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==301: + self.state = 1681 + self.whereClause() + + + pass + elif token in [156]: + localctx = SqlBaseParser.MergeIntoTableContext(self, localctx) + self.enterOuterAlt(localctx, 5) + self.state = 1684 + self.match(SqlBaseParser.MERGE) + self.state = 1685 + self.match(SqlBaseParser.INTO) + self.state = 1686 + localctx.target = self.multipartIdentifier() + self.state = 1687 + localctx.targetAlias = self.tableAlias() + self.state = 1688 + self.match(SqlBaseParser.USING) + self.state = 1694 + self._errHandler.sync(self) + token = self._input.LA(1) + if token in [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 331, 341, 342]: + self.state = 1689 + localctx.source = self.multipartIdentifier() + pass + elif token in [2]: + self.state = 1690 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 1691 + localctx.sourceQuery = self.query() + self.state = 1692 + self.match(SqlBaseParser.RIGHT_PAREN) + pass + else: + raise NoViableAltException(self) + + self.state = 1696 + localctx.sourceAlias = self.tableAlias() + self.state = 1697 + self.match(SqlBaseParser.ON) + self.state = 1698 + localctx.mergeCondition = self.booleanExpression(0) + self.state = 1702 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,181,self._ctx) + while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt==1: + self.state = 1699 + self.matchedClause() + self.state = 1704 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,181,self._ctx) + + self.state = 1708 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,182,self._ctx) + while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt==1: + self.state = 1705 + self.notMatchedClause() + self.state = 1710 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,182,self._ctx) + + self.state = 1714 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==300: + self.state = 1711 + self.notMatchedBySourceClause() + self.state = 1716 + self._errHandler.sync(self) + _la = self._input.LA(1) + + pass + else: + raise NoViableAltException(self) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class QueryOrganizationContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self._sortItem = None # SortItemContext + self.order = list() # of SortItemContexts + self._expression = None # ExpressionContext + self.clusterBy = list() # of ExpressionContexts + self.distributeBy = list() # of ExpressionContexts + self.sort = list() # of SortItemContexts + self.limit = None # ExpressionContext + self.offset = None # ExpressionContext + + def ORDER(self): + return self.getToken(SqlBaseParser.ORDER, 0) + + def BY(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.BY) + else: + return self.getToken(SqlBaseParser.BY, i) + + def CLUSTER(self): + return self.getToken(SqlBaseParser.CLUSTER, 0) + + def DISTRIBUTE(self): + return self.getToken(SqlBaseParser.DISTRIBUTE, 0) + + def SORT(self): + return self.getToken(SqlBaseParser.SORT, 0) + + def windowClause(self): + return self.getTypedRuleContext(SqlBaseParser.WindowClauseContext,0) + + + def LIMIT(self): + return self.getToken(SqlBaseParser.LIMIT, 0) + + def OFFSET(self): + return self.getToken(SqlBaseParser.OFFSET, 0) + + def sortItem(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.SortItemContext) + else: + return self.getTypedRuleContext(SqlBaseParser.SortItemContext,i) + + + def expression(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.ExpressionContext) + else: + return self.getTypedRuleContext(SqlBaseParser.ExpressionContext,i) + + + def ALL(self): + return self.getToken(SqlBaseParser.ALL, 0) + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def getRuleIndex(self): + return SqlBaseParser.RULE_queryOrganization + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterQueryOrganization" ): + listener.enterQueryOrganization(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitQueryOrganization" ): + listener.exitQueryOrganization(self) + + + + + def queryOrganization(self): + + localctx = SqlBaseParser.QueryOrganizationContext(self, self._ctx, self.state) + self.enterRule(localctx, 84, self.RULE_queryOrganization) + try: + self.enterOuterAlt(localctx, 1) + self.state = 1729 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,186,self._ctx) + if la_ == 1: + self.state = 1719 + self.match(SqlBaseParser.ORDER) + self.state = 1720 + self.match(SqlBaseParser.BY) + self.state = 1721 + localctx._sortItem = self.sortItem() + localctx.order.append(localctx._sortItem) + self.state = 1726 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,185,self._ctx) + while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt==1: + self.state = 1722 + self.match(SqlBaseParser.COMMA) + self.state = 1723 + localctx._sortItem = self.sortItem() + localctx.order.append(localctx._sortItem) + self.state = 1728 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,185,self._ctx) + + + + self.state = 1741 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,188,self._ctx) + if la_ == 1: + self.state = 1731 + self.match(SqlBaseParser.CLUSTER) + self.state = 1732 + self.match(SqlBaseParser.BY) + self.state = 1733 + localctx._expression = self.expression() + localctx.clusterBy.append(localctx._expression) + self.state = 1738 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,187,self._ctx) + while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt==1: + self.state = 1734 + self.match(SqlBaseParser.COMMA) + self.state = 1735 + localctx._expression = self.expression() + localctx.clusterBy.append(localctx._expression) + self.state = 1740 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,187,self._ctx) + + + + self.state = 1753 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,190,self._ctx) + if la_ == 1: + self.state = 1743 + self.match(SqlBaseParser.DISTRIBUTE) + self.state = 1744 + self.match(SqlBaseParser.BY) + self.state = 1745 + localctx._expression = self.expression() + localctx.distributeBy.append(localctx._expression) + self.state = 1750 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,189,self._ctx) + while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt==1: + self.state = 1746 + self.match(SqlBaseParser.COMMA) + self.state = 1747 + localctx._expression = self.expression() + localctx.distributeBy.append(localctx._expression) + self.state = 1752 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,189,self._ctx) + + + + self.state = 1765 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,192,self._ctx) + if la_ == 1: + self.state = 1755 + self.match(SqlBaseParser.SORT) + self.state = 1756 + self.match(SqlBaseParser.BY) + self.state = 1757 + localctx._sortItem = self.sortItem() + localctx.sort.append(localctx._sortItem) + self.state = 1762 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,191,self._ctx) + while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt==1: + self.state = 1758 + self.match(SqlBaseParser.COMMA) + self.state = 1759 + localctx._sortItem = self.sortItem() + localctx.sort.append(localctx._sortItem) + self.state = 1764 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,191,self._ctx) + + + + self.state = 1768 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,193,self._ctx) + if la_ == 1: + self.state = 1767 + self.windowClause() + + + self.state = 1775 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,195,self._ctx) + if la_ == 1: + self.state = 1770 + self.match(SqlBaseParser.LIMIT) + self.state = 1773 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,194,self._ctx) + if la_ == 1: + self.state = 1771 + self.match(SqlBaseParser.ALL) + pass + + elif la_ == 2: + self.state = 1772 + localctx.limit = self.expression() + pass + + + + + self.state = 1779 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,196,self._ctx) + if la_ == 1: + self.state = 1777 + self.match(SqlBaseParser.OFFSET) + self.state = 1778 + localctx.offset = self.expression() + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class MultiInsertQueryBodyContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def insertInto(self): + return self.getTypedRuleContext(SqlBaseParser.InsertIntoContext,0) + + + def fromStatementBody(self): + return self.getTypedRuleContext(SqlBaseParser.FromStatementBodyContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_multiInsertQueryBody + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterMultiInsertQueryBody" ): + listener.enterMultiInsertQueryBody(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitMultiInsertQueryBody" ): + listener.exitMultiInsertQueryBody(self) + + + + + def multiInsertQueryBody(self): + + localctx = SqlBaseParser.MultiInsertQueryBodyContext(self, self._ctx, self.state) + self.enterRule(localctx, 86, self.RULE_multiInsertQueryBody) + try: + self.enterOuterAlt(localctx, 1) + self.state = 1781 + self.insertInto() + self.state = 1782 + self.fromStatementBody() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class QueryTermContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + + def getRuleIndex(self): + return SqlBaseParser.RULE_queryTerm + + + def copyFrom(self, ctx:ParserRuleContext): + super().copyFrom(ctx) + + + class QueryTermDefaultContext(QueryTermContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.QueryTermContext + super().__init__(parser) + self.copyFrom(ctx) + + def queryPrimary(self): + return self.getTypedRuleContext(SqlBaseParser.QueryPrimaryContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterQueryTermDefault" ): + listener.enterQueryTermDefault(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitQueryTermDefault" ): + listener.exitQueryTermDefault(self) + + + class SetOperationContext(QueryTermContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.QueryTermContext + super().__init__(parser) + self.left = None # QueryTermContext + self.operator = None # Token + self.right = None # QueryTermContext + self.copyFrom(ctx) + + def queryTerm(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.QueryTermContext) + else: + return self.getTypedRuleContext(SqlBaseParser.QueryTermContext,i) + + def INTERSECT(self): + return self.getToken(SqlBaseParser.INTERSECT, 0) + def UNION(self): + return self.getToken(SqlBaseParser.UNION, 0) + def EXCEPT(self): + return self.getToken(SqlBaseParser.EXCEPT, 0) + def SETMINUS(self): + return self.getToken(SqlBaseParser.SETMINUS, 0) + def setQuantifier(self): + return self.getTypedRuleContext(SqlBaseParser.SetQuantifierContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSetOperation" ): + listener.enterSetOperation(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSetOperation" ): + listener.exitSetOperation(self) + + + + def queryTerm(self, _p:int=0): + _parentctx = self._ctx + _parentState = self.state + localctx = SqlBaseParser.QueryTermContext(self, self._ctx, _parentState) + _prevctx = localctx + _startState = 88 + self.enterRecursionRule(localctx, 88, self.RULE_queryTerm, _p) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + localctx = SqlBaseParser.QueryTermDefaultContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + + self.state = 1785 + self.queryPrimary() + self._ctx.stop = self._input.LT(-1) + self.state = 1795 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,198,self._ctx) + while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt==1: + if self._parseListeners is not None: + self.triggerExitRuleEvent() + _prevctx = localctx + localctx = SqlBaseParser.SetOperationContext(self, SqlBaseParser.QueryTermContext(self, _parentctx, _parentState)) + localctx.left = _prevctx + self.pushNewRecursionContext(localctx, _startState, self.RULE_queryTerm) + self.state = 1787 + if not self.precpred(self._ctx, 1): + from antlr4.error.Errors import FailedPredicateException + raise FailedPredicateException(self, "self.precpred(self._ctx, 1)") + self.state = 1788 + localctx.operator = self._input.LT(1) + _la = self._input.LA(1) + if not(_la==87 or _la==130 or _la==240 or _la==284): + localctx.operator = self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 1790 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==10 or _la==79: + self.state = 1789 + self.setQuantifier() + + + self.state = 1792 + localctx.right = self.queryTerm(2) + self.state = 1797 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,198,self._ctx) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.unrollRecursionContexts(_parentctx) + return localctx + + + class QueryPrimaryContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + + def getRuleIndex(self): + return SqlBaseParser.RULE_queryPrimary + + + def copyFrom(self, ctx:ParserRuleContext): + super().copyFrom(ctx) + + + + class SubqueryContext(QueryPrimaryContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.QueryPrimaryContext + super().__init__(parser) + self.copyFrom(ctx) + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + def query(self): + return self.getTypedRuleContext(SqlBaseParser.QueryContext,0) + + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSubquery" ): + listener.enterSubquery(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSubquery" ): + listener.exitSubquery(self) + + + class QueryPrimaryDefaultContext(QueryPrimaryContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.QueryPrimaryContext + super().__init__(parser) + self.copyFrom(ctx) + + def querySpecification(self): + return self.getTypedRuleContext(SqlBaseParser.QuerySpecificationContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterQueryPrimaryDefault" ): + listener.enterQueryPrimaryDefault(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitQueryPrimaryDefault" ): + listener.exitQueryPrimaryDefault(self) + + + class InlineTableDefault1Context(QueryPrimaryContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.QueryPrimaryContext + super().__init__(parser) + self.copyFrom(ctx) + + def inlineTable(self): + return self.getTypedRuleContext(SqlBaseParser.InlineTableContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterInlineTableDefault1" ): + listener.enterInlineTableDefault1(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitInlineTableDefault1" ): + listener.exitInlineTableDefault1(self) + + + class FromStmtContext(QueryPrimaryContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.QueryPrimaryContext + super().__init__(parser) + self.copyFrom(ctx) + + def fromStatement(self): + return self.getTypedRuleContext(SqlBaseParser.FromStatementContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterFromStmt" ): + listener.enterFromStmt(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitFromStmt" ): + listener.exitFromStmt(self) + + + class TableContext(QueryPrimaryContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.QueryPrimaryContext + super().__init__(parser) + self.copyFrom(ctx) + + def TABLE(self): + return self.getToken(SqlBaseParser.TABLE, 0) + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterTable" ): + listener.enterTable(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitTable" ): + listener.exitTable(self) + + + + def queryPrimary(self): + + localctx = SqlBaseParser.QueryPrimaryContext(self, self._ctx, self.state) + self.enterRule(localctx, 90, self.RULE_queryPrimary) + try: + self.state = 1807 + self._errHandler.sync(self) + token = self._input.LA(1) + if token in [154, 210, 233]: + localctx = SqlBaseParser.QueryPrimaryDefaultContext(self, localctx) + self.enterOuterAlt(localctx, 1) + self.state = 1798 + self.querySpecification() + pass + elif token in [107]: + localctx = SqlBaseParser.FromStmtContext(self, localctx) + self.enterOuterAlt(localctx, 2) + self.state = 1799 + self.fromStatement() + pass + elif token in [258]: + localctx = SqlBaseParser.TableContext(self, localctx) + self.enterOuterAlt(localctx, 3) + self.state = 1800 + self.match(SqlBaseParser.TABLE) + self.state = 1801 + self.multipartIdentifier() + pass + elif token in [294]: + localctx = SqlBaseParser.InlineTableDefault1Context(self, localctx) + self.enterOuterAlt(localctx, 4) + self.state = 1802 + self.inlineTable() + pass + elif token in [2]: + localctx = SqlBaseParser.SubqueryContext(self, localctx) + self.enterOuterAlt(localctx, 5) + self.state = 1803 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 1804 + self.query() + self.state = 1805 + self.match(SqlBaseParser.RIGHT_PAREN) + pass + else: + raise NoViableAltException(self) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class SortItemContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self.ordering = None # Token + self.nullOrder = None # Token + + def expression(self): + return self.getTypedRuleContext(SqlBaseParser.ExpressionContext,0) + + + def NULLS(self): + return self.getToken(SqlBaseParser.NULLS, 0) + + def ASC(self): + return self.getToken(SqlBaseParser.ASC, 0) + + def DESC(self): + return self.getToken(SqlBaseParser.DESC, 0) + + def LAST(self): + return self.getToken(SqlBaseParser.LAST, 0) + + def FIRST(self): + return self.getToken(SqlBaseParser.FIRST, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_sortItem + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSortItem" ): + listener.enterSortItem(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSortItem" ): + listener.exitSortItem(self) + + + + + def sortItem(self): + + localctx = SqlBaseParser.SortItemContext(self, self._ctx, self.state) + self.enterRule(localctx, 92, self.RULE_sortItem) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 1809 + self.expression() + self.state = 1811 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,200,self._ctx) + if la_ == 1: + self.state = 1810 + localctx.ordering = self._input.LT(1) + _la = self._input.LA(1) + if not(_la==21 or _la==74): + localctx.ordering = self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + + + self.state = 1815 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,201,self._ctx) + if la_ == 1: + self.state = 1813 + self.match(SqlBaseParser.NULLS) + self.state = 1814 + localctx.nullOrder = self._input.LT(1) + _la = self._input.LA(1) + if not(_la==101 or _la==137): + localctx.nullOrder = self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class FromStatementContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def fromClause(self): + return self.getTypedRuleContext(SqlBaseParser.FromClauseContext,0) + + + def fromStatementBody(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.FromStatementBodyContext) + else: + return self.getTypedRuleContext(SqlBaseParser.FromStatementBodyContext,i) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_fromStatement + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterFromStatement" ): + listener.enterFromStatement(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitFromStatement" ): + listener.exitFromStatement(self) + + + + + def fromStatement(self): + + localctx = SqlBaseParser.FromStatementContext(self, self._ctx, self.state) + self.enterRule(localctx, 94, self.RULE_fromStatement) + try: + self.enterOuterAlt(localctx, 1) + self.state = 1817 + self.fromClause() + self.state = 1819 + self._errHandler.sync(self) + _alt = 1 + while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt == 1: + self.state = 1818 + self.fromStatementBody() + + else: + raise NoViableAltException(self) + self.state = 1821 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,202,self._ctx) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class FromStatementBodyContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def transformClause(self): + return self.getTypedRuleContext(SqlBaseParser.TransformClauseContext,0) + + + def queryOrganization(self): + return self.getTypedRuleContext(SqlBaseParser.QueryOrganizationContext,0) + + + def whereClause(self): + return self.getTypedRuleContext(SqlBaseParser.WhereClauseContext,0) + + + def selectClause(self): + return self.getTypedRuleContext(SqlBaseParser.SelectClauseContext,0) + + + def lateralView(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.LateralViewContext) + else: + return self.getTypedRuleContext(SqlBaseParser.LateralViewContext,i) + + + def aggregationClause(self): + return self.getTypedRuleContext(SqlBaseParser.AggregationClauseContext,0) + + + def havingClause(self): + return self.getTypedRuleContext(SqlBaseParser.HavingClauseContext,0) + + + def windowClause(self): + return self.getTypedRuleContext(SqlBaseParser.WindowClauseContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_fromStatementBody + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterFromStatementBody" ): + listener.enterFromStatementBody(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitFromStatementBody" ): + listener.exitFromStatementBody(self) + + + + + def fromStatementBody(self): + + localctx = SqlBaseParser.FromStatementBodyContext(self, self._ctx, self.state) + self.enterRule(localctx, 96, self.RULE_fromStatementBody) + try: + self.state = 1850 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,209,self._ctx) + if la_ == 1: + self.enterOuterAlt(localctx, 1) + self.state = 1823 + self.transformClause() + self.state = 1825 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,203,self._ctx) + if la_ == 1: + self.state = 1824 + self.whereClause() + + + self.state = 1827 + self.queryOrganization() + pass + + elif la_ == 2: + self.enterOuterAlt(localctx, 2) + self.state = 1829 + self.selectClause() + self.state = 1833 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,204,self._ctx) + while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt==1: + self.state = 1830 + self.lateralView() + self.state = 1835 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,204,self._ctx) + + self.state = 1837 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,205,self._ctx) + if la_ == 1: + self.state = 1836 + self.whereClause() + + + self.state = 1840 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,206,self._ctx) + if la_ == 1: + self.state = 1839 + self.aggregationClause() + + + self.state = 1843 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,207,self._ctx) + if la_ == 1: + self.state = 1842 + self.havingClause() + + + self.state = 1846 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,208,self._ctx) + if la_ == 1: + self.state = 1845 + self.windowClause() + + + self.state = 1848 + self.queryOrganization() + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class QuerySpecificationContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + + def getRuleIndex(self): + return SqlBaseParser.RULE_querySpecification + + + def copyFrom(self, ctx:ParserRuleContext): + super().copyFrom(ctx) + + + + class RegularQuerySpecificationContext(QuerySpecificationContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.QuerySpecificationContext + super().__init__(parser) + self.copyFrom(ctx) + + def selectClause(self): + return self.getTypedRuleContext(SqlBaseParser.SelectClauseContext,0) + + def fromClause(self): + return self.getTypedRuleContext(SqlBaseParser.FromClauseContext,0) + + def lateralView(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.LateralViewContext) + else: + return self.getTypedRuleContext(SqlBaseParser.LateralViewContext,i) + + def whereClause(self): + return self.getTypedRuleContext(SqlBaseParser.WhereClauseContext,0) + + def aggregationClause(self): + return self.getTypedRuleContext(SqlBaseParser.AggregationClauseContext,0) + + def havingClause(self): + return self.getTypedRuleContext(SqlBaseParser.HavingClauseContext,0) + + def windowClause(self): + return self.getTypedRuleContext(SqlBaseParser.WindowClauseContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterRegularQuerySpecification" ): + listener.enterRegularQuerySpecification(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitRegularQuerySpecification" ): + listener.exitRegularQuerySpecification(self) + + + class TransformQuerySpecificationContext(QuerySpecificationContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.QuerySpecificationContext + super().__init__(parser) + self.copyFrom(ctx) + + def transformClause(self): + return self.getTypedRuleContext(SqlBaseParser.TransformClauseContext,0) + + def fromClause(self): + return self.getTypedRuleContext(SqlBaseParser.FromClauseContext,0) + + def lateralView(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.LateralViewContext) + else: + return self.getTypedRuleContext(SqlBaseParser.LateralViewContext,i) + + def whereClause(self): + return self.getTypedRuleContext(SqlBaseParser.WhereClauseContext,0) + + def aggregationClause(self): + return self.getTypedRuleContext(SqlBaseParser.AggregationClauseContext,0) + + def havingClause(self): + return self.getTypedRuleContext(SqlBaseParser.HavingClauseContext,0) + + def windowClause(self): + return self.getTypedRuleContext(SqlBaseParser.WindowClauseContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterTransformQuerySpecification" ): + listener.enterTransformQuerySpecification(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitTransformQuerySpecification" ): + listener.exitTransformQuerySpecification(self) + + + + def querySpecification(self): + + localctx = SqlBaseParser.QuerySpecificationContext(self, self._ctx, self.state) + self.enterRule(localctx, 98, self.RULE_querySpecification) + try: + self.state = 1896 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,222,self._ctx) + if la_ == 1: + localctx = SqlBaseParser.TransformQuerySpecificationContext(self, localctx) + self.enterOuterAlt(localctx, 1) + self.state = 1852 + self.transformClause() + self.state = 1854 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,210,self._ctx) + if la_ == 1: + self.state = 1853 + self.fromClause() + + + self.state = 1859 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,211,self._ctx) + while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt==1: + self.state = 1856 + self.lateralView() + self.state = 1861 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,211,self._ctx) + + self.state = 1863 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,212,self._ctx) + if la_ == 1: + self.state = 1862 + self.whereClause() + + + self.state = 1866 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,213,self._ctx) + if la_ == 1: + self.state = 1865 + self.aggregationClause() + + + self.state = 1869 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,214,self._ctx) + if la_ == 1: + self.state = 1868 + self.havingClause() + + + self.state = 1872 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,215,self._ctx) + if la_ == 1: + self.state = 1871 + self.windowClause() + + + pass + + elif la_ == 2: + localctx = SqlBaseParser.RegularQuerySpecificationContext(self, localctx) + self.enterOuterAlt(localctx, 2) + self.state = 1874 + self.selectClause() + self.state = 1876 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,216,self._ctx) + if la_ == 1: + self.state = 1875 + self.fromClause() + + + self.state = 1881 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,217,self._ctx) + while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt==1: + self.state = 1878 + self.lateralView() + self.state = 1883 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,217,self._ctx) + + self.state = 1885 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,218,self._ctx) + if la_ == 1: + self.state = 1884 + self.whereClause() + + + self.state = 1888 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,219,self._ctx) + if la_ == 1: + self.state = 1887 + self.aggregationClause() + + + self.state = 1891 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,220,self._ctx) + if la_ == 1: + self.state = 1890 + self.havingClause() + + + self.state = 1894 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,221,self._ctx) + if la_ == 1: + self.state = 1893 + self.windowClause() + + + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class TransformClauseContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self.kind = None # Token + self.inRowFormat = None # RowFormatContext + self.recordWriter = None # StringLitContext + self.script = None # StringLitContext + self.outRowFormat = None # RowFormatContext + self.recordReader = None # StringLitContext + + def USING(self): + return self.getToken(SqlBaseParser.USING, 0) + + def stringLit(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.StringLitContext) + else: + return self.getTypedRuleContext(SqlBaseParser.StringLitContext,i) + + + def SELECT(self): + return self.getToken(SqlBaseParser.SELECT, 0) + + def LEFT_PAREN(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.LEFT_PAREN) + else: + return self.getToken(SqlBaseParser.LEFT_PAREN, i) + + def expressionSeq(self): + return self.getTypedRuleContext(SqlBaseParser.ExpressionSeqContext,0) + + + def RIGHT_PAREN(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.RIGHT_PAREN) + else: + return self.getToken(SqlBaseParser.RIGHT_PAREN, i) + + def TRANSFORM(self): + return self.getToken(SqlBaseParser.TRANSFORM, 0) + + def MAP(self): + return self.getToken(SqlBaseParser.MAP, 0) + + def REDUCE(self): + return self.getToken(SqlBaseParser.REDUCE, 0) + + def RECORDWRITER(self): + return self.getToken(SqlBaseParser.RECORDWRITER, 0) + + def AS(self): + return self.getToken(SqlBaseParser.AS, 0) + + def RECORDREADER(self): + return self.getToken(SqlBaseParser.RECORDREADER, 0) + + def rowFormat(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.RowFormatContext) + else: + return self.getTypedRuleContext(SqlBaseParser.RowFormatContext,i) + + + def setQuantifier(self): + return self.getTypedRuleContext(SqlBaseParser.SetQuantifierContext,0) + + + def identifierSeq(self): + return self.getTypedRuleContext(SqlBaseParser.IdentifierSeqContext,0) + + + def colTypeList(self): + return self.getTypedRuleContext(SqlBaseParser.ColTypeListContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_transformClause + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterTransformClause" ): + listener.enterTransformClause(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitTransformClause" ): + listener.exitTransformClause(self) + + + + + def transformClause(self): + + localctx = SqlBaseParser.TransformClauseContext(self, self._ctx, self.state) + self.enterRule(localctx, 100, self.RULE_transformClause) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 1917 + self._errHandler.sync(self) + token = self._input.LA(1) + if token in [233]: + self.state = 1898 + self.match(SqlBaseParser.SELECT) + self.state = 1899 + localctx.kind = self.match(SqlBaseParser.TRANSFORM) + self.state = 1900 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 1902 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,223,self._ctx) + if la_ == 1: + self.state = 1901 + self.setQuantifier() + + + self.state = 1904 + self.expressionSeq() + self.state = 1905 + self.match(SqlBaseParser.RIGHT_PAREN) + pass + elif token in [154]: + self.state = 1907 + localctx.kind = self.match(SqlBaseParser.MAP) + self.state = 1909 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,224,self._ctx) + if la_ == 1: + self.state = 1908 + self.setQuantifier() + + + self.state = 1911 + self.expressionSeq() + pass + elif token in [210]: + self.state = 1912 + localctx.kind = self.match(SqlBaseParser.REDUCE) + self.state = 1914 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,225,self._ctx) + if la_ == 1: + self.state = 1913 + self.setQuantifier() + + + self.state = 1916 + self.expressionSeq() + pass + else: + raise NoViableAltException(self) + + self.state = 1920 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==227: + self.state = 1919 + localctx.inRowFormat = self.rowFormat() + + + self.state = 1924 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==208: + self.state = 1922 + self.match(SqlBaseParser.RECORDWRITER) + self.state = 1923 + localctx.recordWriter = self.stringLit() + + + self.state = 1926 + self.match(SqlBaseParser.USING) + self.state = 1927 + localctx.script = self.stringLit() + self.state = 1940 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,231,self._ctx) + if la_ == 1: + self.state = 1928 + self.match(SqlBaseParser.AS) + self.state = 1938 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,230,self._ctx) + if la_ == 1: + self.state = 1929 + self.identifierSeq() + pass + + elif la_ == 2: + self.state = 1930 + self.colTypeList() + pass + + elif la_ == 3: + self.state = 1931 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 1934 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,229,self._ctx) + if la_ == 1: + self.state = 1932 + self.identifierSeq() + pass + + elif la_ == 2: + self.state = 1933 + self.colTypeList() + pass + + + self.state = 1936 + self.match(SqlBaseParser.RIGHT_PAREN) + pass + + + + + self.state = 1943 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,232,self._ctx) + if la_ == 1: + self.state = 1942 + localctx.outRowFormat = self.rowFormat() + + + self.state = 1947 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,233,self._ctx) + if la_ == 1: + self.state = 1945 + self.match(SqlBaseParser.RECORDREADER) + self.state = 1946 + localctx.recordReader = self.stringLit() + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class SelectClauseContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self._hint = None # HintContext + self.hints = list() # of HintContexts + + def SELECT(self): + return self.getToken(SqlBaseParser.SELECT, 0) + + def namedExpressionSeq(self): + return self.getTypedRuleContext(SqlBaseParser.NamedExpressionSeqContext,0) + + + def setQuantifier(self): + return self.getTypedRuleContext(SqlBaseParser.SetQuantifierContext,0) + + + def hint(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.HintContext) + else: + return self.getTypedRuleContext(SqlBaseParser.HintContext,i) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_selectClause + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSelectClause" ): + listener.enterSelectClause(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSelectClause" ): + listener.exitSelectClause(self) + + + + + def selectClause(self): + + localctx = SqlBaseParser.SelectClauseContext(self, self._ctx, self.state) + self.enterRule(localctx, 102, self.RULE_selectClause) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 1949 + self.match(SqlBaseParser.SELECT) + self.state = 1953 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==328: + self.state = 1950 + localctx._hint = self.hint() + localctx.hints.append(localctx._hint) + self.state = 1955 + self._errHandler.sync(self) + _la = self._input.LA(1) + + self.state = 1957 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,235,self._ctx) + if la_ == 1: + self.state = 1956 + self.setQuantifier() + + + self.state = 1959 + self.namedExpressionSeq() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class SetClauseContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def SET(self): + return self.getToken(SqlBaseParser.SET, 0) + + def assignmentList(self): + return self.getTypedRuleContext(SqlBaseParser.AssignmentListContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_setClause + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSetClause" ): + listener.enterSetClause(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSetClause" ): + listener.exitSetClause(self) + + + + + def setClause(self): + + localctx = SqlBaseParser.SetClauseContext(self, self._ctx, self.state) + self.enterRule(localctx, 104, self.RULE_setClause) + try: + self.enterOuterAlt(localctx, 1) + self.state = 1961 + self.match(SqlBaseParser.SET) + self.state = 1962 + self.assignmentList() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class MatchedClauseContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self.matchedCond = None # BooleanExpressionContext + + def WHEN(self): + return self.getToken(SqlBaseParser.WHEN, 0) + + def MATCHED(self): + return self.getToken(SqlBaseParser.MATCHED, 0) + + def THEN(self): + return self.getToken(SqlBaseParser.THEN, 0) + + def matchedAction(self): + return self.getTypedRuleContext(SqlBaseParser.MatchedActionContext,0) + + + def AND(self): + return self.getToken(SqlBaseParser.AND, 0) + + def booleanExpression(self): + return self.getTypedRuleContext(SqlBaseParser.BooleanExpressionContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_matchedClause + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterMatchedClause" ): + listener.enterMatchedClause(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitMatchedClause" ): + listener.exitMatchedClause(self) + + + + + def matchedClause(self): + + localctx = SqlBaseParser.MatchedClauseContext(self, self._ctx, self.state) + self.enterRule(localctx, 106, self.RULE_matchedClause) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 1964 + self.match(SqlBaseParser.WHEN) + self.state = 1965 + self.match(SqlBaseParser.MATCHED) + self.state = 1968 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==14: + self.state = 1966 + self.match(SqlBaseParser.AND) + self.state = 1967 + localctx.matchedCond = self.booleanExpression(0) + + + self.state = 1970 + self.match(SqlBaseParser.THEN) + self.state = 1971 + self.matchedAction() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class NotMatchedClauseContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self.notMatchedCond = None # BooleanExpressionContext + + def WHEN(self): + return self.getToken(SqlBaseParser.WHEN, 0) + + def NOT(self): + return self.getToken(SqlBaseParser.NOT, 0) + + def MATCHED(self): + return self.getToken(SqlBaseParser.MATCHED, 0) + + def THEN(self): + return self.getToken(SqlBaseParser.THEN, 0) + + def notMatchedAction(self): + return self.getTypedRuleContext(SqlBaseParser.NotMatchedActionContext,0) + + + def BY(self): + return self.getToken(SqlBaseParser.BY, 0) + + def TARGET(self): + return self.getToken(SqlBaseParser.TARGET, 0) + + def AND(self): + return self.getToken(SqlBaseParser.AND, 0) + + def booleanExpression(self): + return self.getTypedRuleContext(SqlBaseParser.BooleanExpressionContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_notMatchedClause + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterNotMatchedClause" ): + listener.enterNotMatchedClause(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitNotMatchedClause" ): + listener.exitNotMatchedClause(self) + + + + + def notMatchedClause(self): + + localctx = SqlBaseParser.NotMatchedClauseContext(self, self._ctx, self.state) + self.enterRule(localctx, 108, self.RULE_notMatchedClause) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 1973 + self.match(SqlBaseParser.WHEN) + self.state = 1974 + self.match(SqlBaseParser.NOT) + self.state = 1975 + self.match(SqlBaseParser.MATCHED) + self.state = 1978 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==28: + self.state = 1976 + self.match(SqlBaseParser.BY) + self.state = 1977 + self.match(SqlBaseParser.TARGET) + + + self.state = 1982 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==14: + self.state = 1980 + self.match(SqlBaseParser.AND) + self.state = 1981 + localctx.notMatchedCond = self.booleanExpression(0) + + + self.state = 1984 + self.match(SqlBaseParser.THEN) + self.state = 1985 + self.notMatchedAction() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class NotMatchedBySourceClauseContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self.notMatchedBySourceCond = None # BooleanExpressionContext + + def WHEN(self): + return self.getToken(SqlBaseParser.WHEN, 0) + + def NOT(self): + return self.getToken(SqlBaseParser.NOT, 0) + + def MATCHED(self): + return self.getToken(SqlBaseParser.MATCHED, 0) + + def BY(self): + return self.getToken(SqlBaseParser.BY, 0) + + def SOURCE(self): + return self.getToken(SqlBaseParser.SOURCE, 0) + + def THEN(self): + return self.getToken(SqlBaseParser.THEN, 0) + + def notMatchedBySourceAction(self): + return self.getTypedRuleContext(SqlBaseParser.NotMatchedBySourceActionContext,0) + + + def AND(self): + return self.getToken(SqlBaseParser.AND, 0) + + def booleanExpression(self): + return self.getTypedRuleContext(SqlBaseParser.BooleanExpressionContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_notMatchedBySourceClause + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterNotMatchedBySourceClause" ): + listener.enterNotMatchedBySourceClause(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitNotMatchedBySourceClause" ): + listener.exitNotMatchedBySourceClause(self) + + + + + def notMatchedBySourceClause(self): + + localctx = SqlBaseParser.NotMatchedBySourceClauseContext(self, self._ctx, self.state) + self.enterRule(localctx, 110, self.RULE_notMatchedBySourceClause) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 1987 + self.match(SqlBaseParser.WHEN) + self.state = 1988 + self.match(SqlBaseParser.NOT) + self.state = 1989 + self.match(SqlBaseParser.MATCHED) + self.state = 1990 + self.match(SqlBaseParser.BY) + self.state = 1991 + self.match(SqlBaseParser.SOURCE) + self.state = 1994 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==14: + self.state = 1992 + self.match(SqlBaseParser.AND) + self.state = 1993 + localctx.notMatchedBySourceCond = self.booleanExpression(0) + + + self.state = 1996 + self.match(SqlBaseParser.THEN) + self.state = 1997 + self.notMatchedBySourceAction() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class MatchedActionContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def DELETE(self): + return self.getToken(SqlBaseParser.DELETE, 0) + + def UPDATE(self): + return self.getToken(SqlBaseParser.UPDATE, 0) + + def SET(self): + return self.getToken(SqlBaseParser.SET, 0) + + def ASTERISK(self): + return self.getToken(SqlBaseParser.ASTERISK, 0) + + def assignmentList(self): + return self.getTypedRuleContext(SqlBaseParser.AssignmentListContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_matchedAction + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterMatchedAction" ): + listener.enterMatchedAction(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitMatchedAction" ): + listener.exitMatchedAction(self) + + + + + def matchedAction(self): + + localctx = SqlBaseParser.MatchedActionContext(self, self._ctx, self.state) + self.enterRule(localctx, 112, self.RULE_matchedAction) + try: + self.state = 2006 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,240,self._ctx) + if la_ == 1: + self.enterOuterAlt(localctx, 1) + self.state = 1999 + self.match(SqlBaseParser.DELETE) + pass + + elif la_ == 2: + self.enterOuterAlt(localctx, 2) + self.state = 2000 + self.match(SqlBaseParser.UPDATE) + self.state = 2001 + self.match(SqlBaseParser.SET) + self.state = 2002 + self.match(SqlBaseParser.ASTERISK) + pass + + elif la_ == 3: + self.enterOuterAlt(localctx, 3) + self.state = 2003 + self.match(SqlBaseParser.UPDATE) + self.state = 2004 + self.match(SqlBaseParser.SET) + self.state = 2005 + self.assignmentList() + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class NotMatchedActionContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self.columns = None # MultipartIdentifierListContext + + def INSERT(self): + return self.getToken(SqlBaseParser.INSERT, 0) + + def ASTERISK(self): + return self.getToken(SqlBaseParser.ASTERISK, 0) + + def LEFT_PAREN(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.LEFT_PAREN) + else: + return self.getToken(SqlBaseParser.LEFT_PAREN, i) + + def RIGHT_PAREN(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.RIGHT_PAREN) + else: + return self.getToken(SqlBaseParser.RIGHT_PAREN, i) + + def VALUES(self): + return self.getToken(SqlBaseParser.VALUES, 0) + + def expression(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.ExpressionContext) + else: + return self.getTypedRuleContext(SqlBaseParser.ExpressionContext,i) + + + def multipartIdentifierList(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierListContext,0) + + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def getRuleIndex(self): + return SqlBaseParser.RULE_notMatchedAction + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterNotMatchedAction" ): + listener.enterNotMatchedAction(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitNotMatchedAction" ): + listener.exitNotMatchedAction(self) + + + + + def notMatchedAction(self): + + localctx = SqlBaseParser.NotMatchedActionContext(self, self._ctx, self.state) + self.enterRule(localctx, 114, self.RULE_notMatchedAction) + self._la = 0 # Token type + try: + self.state = 2026 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,242,self._ctx) + if la_ == 1: + self.enterOuterAlt(localctx, 1) + self.state = 2008 + self.match(SqlBaseParser.INSERT) + self.state = 2009 + self.match(SqlBaseParser.ASTERISK) + pass + + elif la_ == 2: + self.enterOuterAlt(localctx, 2) + self.state = 2010 + self.match(SqlBaseParser.INSERT) + self.state = 2011 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2012 + localctx.columns = self.multipartIdentifierList() + self.state = 2013 + self.match(SqlBaseParser.RIGHT_PAREN) + self.state = 2014 + self.match(SqlBaseParser.VALUES) + self.state = 2015 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2016 + self.expression() + self.state = 2021 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 2017 + self.match(SqlBaseParser.COMMA) + self.state = 2018 + self.expression() + self.state = 2023 + self._errHandler.sync(self) + _la = self._input.LA(1) + + self.state = 2024 + self.match(SqlBaseParser.RIGHT_PAREN) + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class NotMatchedBySourceActionContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def DELETE(self): + return self.getToken(SqlBaseParser.DELETE, 0) + + def UPDATE(self): + return self.getToken(SqlBaseParser.UPDATE, 0) + + def SET(self): + return self.getToken(SqlBaseParser.SET, 0) + + def assignmentList(self): + return self.getTypedRuleContext(SqlBaseParser.AssignmentListContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_notMatchedBySourceAction + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterNotMatchedBySourceAction" ): + listener.enterNotMatchedBySourceAction(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitNotMatchedBySourceAction" ): + listener.exitNotMatchedBySourceAction(self) + + + + + def notMatchedBySourceAction(self): + + localctx = SqlBaseParser.NotMatchedBySourceActionContext(self, self._ctx, self.state) + self.enterRule(localctx, 116, self.RULE_notMatchedBySourceAction) + try: + self.state = 2032 + self._errHandler.sync(self) + token = self._input.LA(1) + if token in [72]: + self.enterOuterAlt(localctx, 1) + self.state = 2028 + self.match(SqlBaseParser.DELETE) + pass + elif token in [290]: + self.enterOuterAlt(localctx, 2) + self.state = 2029 + self.match(SqlBaseParser.UPDATE) + self.state = 2030 + self.match(SqlBaseParser.SET) + self.state = 2031 + self.assignmentList() + pass + else: + raise NoViableAltException(self) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class AssignmentListContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def assignment(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.AssignmentContext) + else: + return self.getTypedRuleContext(SqlBaseParser.AssignmentContext,i) + + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def getRuleIndex(self): + return SqlBaseParser.RULE_assignmentList + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterAssignmentList" ): + listener.enterAssignmentList(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitAssignmentList" ): + listener.exitAssignmentList(self) + + + + + def assignmentList(self): + + localctx = SqlBaseParser.AssignmentListContext(self, self._ctx, self.state) + self.enterRule(localctx, 118, self.RULE_assignmentList) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 2034 + self.assignment() + self.state = 2039 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 2035 + self.match(SqlBaseParser.COMMA) + self.state = 2036 + self.assignment() + self.state = 2041 + self._errHandler.sync(self) + _la = self._input.LA(1) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class AssignmentContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self.key = None # MultipartIdentifierContext + self.value = None # ExpressionContext + + def EQ(self): + return self.getToken(SqlBaseParser.EQ, 0) + + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + + def expression(self): + return self.getTypedRuleContext(SqlBaseParser.ExpressionContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_assignment + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterAssignment" ): + listener.enterAssignment(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitAssignment" ): + listener.exitAssignment(self) + + + + + def assignment(self): + + localctx = SqlBaseParser.AssignmentContext(self, self._ctx, self.state) + self.enterRule(localctx, 120, self.RULE_assignment) + try: + self.enterOuterAlt(localctx, 1) + self.state = 2042 + localctx.key = self.multipartIdentifier() + self.state = 2043 + self.match(SqlBaseParser.EQ) + self.state = 2044 + localctx.value = self.expression() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class WhereClauseContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def WHERE(self): + return self.getToken(SqlBaseParser.WHERE, 0) + + def booleanExpression(self): + return self.getTypedRuleContext(SqlBaseParser.BooleanExpressionContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_whereClause + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterWhereClause" ): + listener.enterWhereClause(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitWhereClause" ): + listener.exitWhereClause(self) + + + + + def whereClause(self): + + localctx = SqlBaseParser.WhereClauseContext(self, self._ctx, self.state) + self.enterRule(localctx, 122, self.RULE_whereClause) + try: + self.enterOuterAlt(localctx, 1) + self.state = 2046 + self.match(SqlBaseParser.WHERE) + self.state = 2047 + self.booleanExpression(0) + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class HavingClauseContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def HAVING(self): + return self.getToken(SqlBaseParser.HAVING, 0) + + def booleanExpression(self): + return self.getTypedRuleContext(SqlBaseParser.BooleanExpressionContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_havingClause + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterHavingClause" ): + listener.enterHavingClause(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitHavingClause" ): + listener.exitHavingClause(self) + + + + + def havingClause(self): + + localctx = SqlBaseParser.HavingClauseContext(self, self._ctx, self.state) + self.enterRule(localctx, 124, self.RULE_havingClause) + try: + self.enterOuterAlt(localctx, 1) + self.state = 2049 + self.match(SqlBaseParser.HAVING) + self.state = 2050 + self.booleanExpression(0) + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class HintContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self._hintStatement = None # HintStatementContext + self.hintStatements = list() # of HintStatementContexts + + def HENT_START(self): + return self.getToken(SqlBaseParser.HENT_START, 0) + + def HENT_END(self): + return self.getToken(SqlBaseParser.HENT_END, 0) + + def hintStatement(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.HintStatementContext) + else: + return self.getTypedRuleContext(SqlBaseParser.HintStatementContext,i) + + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def getRuleIndex(self): + return SqlBaseParser.RULE_hint + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterHint" ): + listener.enterHint(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitHint" ): + listener.exitHint(self) + + + + + def hint(self): + + localctx = SqlBaseParser.HintContext(self, self._ctx, self.state) + self.enterRule(localctx, 126, self.RULE_hint) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 2052 + self.match(SqlBaseParser.HENT_START) + self.state = 2053 + localctx._hintStatement = self.hintStatement() + localctx.hintStatements.append(localctx._hintStatement) + self.state = 2060 + self._errHandler.sync(self) + _la = self._input.LA(1) + while (((_la) & ~0x3f) == 0 and ((1 << _la) & -240) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & -1) != 0) or ((((_la - 128)) & ~0x3f) == 0 and ((1 << (_la - 128)) & -1) != 0) or ((((_la - 192)) & ~0x3f) == 0 and ((1 << (_la - 192)) & -1) != 0) or ((((_la - 256)) & ~0x3f) == 0 and ((1 << (_la - 256)) & 4503599627370495) != 0) or ((((_la - 331)) & ~0x3f) == 0 and ((1 << (_la - 331)) & 3073) != 0): + self.state = 2055 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==4: + self.state = 2054 + self.match(SqlBaseParser.COMMA) + + + self.state = 2057 + localctx._hintStatement = self.hintStatement() + localctx.hintStatements.append(localctx._hintStatement) + self.state = 2062 + self._errHandler.sync(self) + _la = self._input.LA(1) + + self.state = 2063 + self.match(SqlBaseParser.HENT_END) + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class HintStatementContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self.hintName = None # IdentifierContext + self._primaryExpression = None # PrimaryExpressionContext + self.parameters = list() # of PrimaryExpressionContexts + + def identifier(self): + return self.getTypedRuleContext(SqlBaseParser.IdentifierContext,0) + + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + + def primaryExpression(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.PrimaryExpressionContext) + else: + return self.getTypedRuleContext(SqlBaseParser.PrimaryExpressionContext,i) + + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def getRuleIndex(self): + return SqlBaseParser.RULE_hintStatement + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterHintStatement" ): + listener.enterHintStatement(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitHintStatement" ): + listener.exitHintStatement(self) + + + + + def hintStatement(self): + + localctx = SqlBaseParser.HintStatementContext(self, self._ctx, self.state) + self.enterRule(localctx, 128, self.RULE_hintStatement) + self._la = 0 # Token type + try: + self.state = 2078 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,248,self._ctx) + if la_ == 1: + self.enterOuterAlt(localctx, 1) + self.state = 2065 + localctx.hintName = self.identifier() + pass + + elif la_ == 2: + self.enterOuterAlt(localctx, 2) + self.state = 2066 + localctx.hintName = self.identifier() + self.state = 2067 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2068 + localctx._primaryExpression = self.primaryExpression(0) + localctx.parameters.append(localctx._primaryExpression) + self.state = 2073 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 2069 + self.match(SqlBaseParser.COMMA) + self.state = 2070 + localctx._primaryExpression = self.primaryExpression(0) + localctx.parameters.append(localctx._primaryExpression) + self.state = 2075 + self._errHandler.sync(self) + _la = self._input.LA(1) + + self.state = 2076 + self.match(SqlBaseParser.RIGHT_PAREN) + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class FromClauseContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def FROM(self): + return self.getToken(SqlBaseParser.FROM, 0) + + def relation(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.RelationContext) + else: + return self.getTypedRuleContext(SqlBaseParser.RelationContext,i) + + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def lateralView(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.LateralViewContext) + else: + return self.getTypedRuleContext(SqlBaseParser.LateralViewContext,i) + + + def pivotClause(self): + return self.getTypedRuleContext(SqlBaseParser.PivotClauseContext,0) + + + def unpivotClause(self): + return self.getTypedRuleContext(SqlBaseParser.UnpivotClauseContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_fromClause + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterFromClause" ): + listener.enterFromClause(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitFromClause" ): + listener.exitFromClause(self) + + + + + def fromClause(self): + + localctx = SqlBaseParser.FromClauseContext(self, self._ctx, self.state) + self.enterRule(localctx, 130, self.RULE_fromClause) + try: + self.enterOuterAlt(localctx, 1) + self.state = 2080 + self.match(SqlBaseParser.FROM) + self.state = 2081 + self.relation() + self.state = 2086 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,249,self._ctx) + while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt==1: + self.state = 2082 + self.match(SqlBaseParser.COMMA) + self.state = 2083 + self.relation() + self.state = 2088 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,249,self._ctx) + + self.state = 2092 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,250,self._ctx) + while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt==1: + self.state = 2089 + self.lateralView() + self.state = 2094 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,250,self._ctx) + + self.state = 2096 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,251,self._ctx) + if la_ == 1: + self.state = 2095 + self.pivotClause() + + + self.state = 2099 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,252,self._ctx) + if la_ == 1: + self.state = 2098 + self.unpivotClause() + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class TemporalClauseContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self.timestamp = None # ValueExpressionContext + + def AS(self): + return self.getToken(SqlBaseParser.AS, 0) + + def OF(self): + return self.getToken(SqlBaseParser.OF, 0) + + def version(self): + return self.getTypedRuleContext(SqlBaseParser.VersionContext,0) + + + def SYSTEM_VERSION(self): + return self.getToken(SqlBaseParser.SYSTEM_VERSION, 0) + + def VERSION(self): + return self.getToken(SqlBaseParser.VERSION, 0) + + def FOR(self): + return self.getToken(SqlBaseParser.FOR, 0) + + def SYSTEM_TIME(self): + return self.getToken(SqlBaseParser.SYSTEM_TIME, 0) + + def TIMESTAMP(self): + return self.getToken(SqlBaseParser.TIMESTAMP, 0) + + def valueExpression(self): + return self.getTypedRuleContext(SqlBaseParser.ValueExpressionContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_temporalClause + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterTemporalClause" ): + listener.enterTemporalClause(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitTemporalClause" ): + listener.exitTemporalClause(self) + + + + + def temporalClause(self): + + localctx = SqlBaseParser.TemporalClauseContext(self, self._ctx, self.state) + self.enterRule(localctx, 132, self.RULE_temporalClause) + self._la = 0 # Token type + try: + self.state = 2115 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,255,self._ctx) + if la_ == 1: + self.enterOuterAlt(localctx, 1) + self.state = 2102 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==103: + self.state = 2101 + self.match(SqlBaseParser.FOR) + + + self.state = 2104 + _la = self._input.LA(1) + if not(_la==257 or _la==295): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 2105 + self.match(SqlBaseParser.AS) + self.state = 2106 + self.match(SqlBaseParser.OF) + self.state = 2107 + self.version() + pass + + elif la_ == 2: + self.enterOuterAlt(localctx, 2) + self.state = 2109 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==103: + self.state = 2108 + self.match(SqlBaseParser.FOR) + + + self.state = 2111 + _la = self._input.LA(1) + if not(_la==256 or _la==267): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 2112 + self.match(SqlBaseParser.AS) + self.state = 2113 + self.match(SqlBaseParser.OF) + self.state = 2114 + localctx.timestamp = self.valueExpression(0) + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class AggregationClauseContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self._groupByClause = None # GroupByClauseContext + self.groupingExpressionsWithGroupingAnalytics = list() # of GroupByClauseContexts + self._expression = None # ExpressionContext + self.groupingExpressions = list() # of ExpressionContexts + self.kind = None # Token + + def GROUP(self): + return self.getToken(SqlBaseParser.GROUP, 0) + + def BY(self): + return self.getToken(SqlBaseParser.BY, 0) + + def groupByClause(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.GroupByClauseContext) + else: + return self.getTypedRuleContext(SqlBaseParser.GroupByClauseContext,i) + + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def expression(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.ExpressionContext) + else: + return self.getTypedRuleContext(SqlBaseParser.ExpressionContext,i) + + + def WITH(self): + return self.getToken(SqlBaseParser.WITH, 0) + + def SETS(self): + return self.getToken(SqlBaseParser.SETS, 0) + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + + def groupingSet(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.GroupingSetContext) + else: + return self.getTypedRuleContext(SqlBaseParser.GroupingSetContext,i) + + + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + + def ROLLUP(self): + return self.getToken(SqlBaseParser.ROLLUP, 0) + + def CUBE(self): + return self.getToken(SqlBaseParser.CUBE, 0) + + def GROUPING(self): + return self.getToken(SqlBaseParser.GROUPING, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_aggregationClause + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterAggregationClause" ): + listener.enterAggregationClause(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitAggregationClause" ): + listener.exitAggregationClause(self) + + + + + def aggregationClause(self): + + localctx = SqlBaseParser.AggregationClauseContext(self, self._ctx, self.state) + self.enterRule(localctx, 134, self.RULE_aggregationClause) + self._la = 0 # Token type + try: + self.state = 2156 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,260,self._ctx) + if la_ == 1: + self.enterOuterAlt(localctx, 1) + self.state = 2117 + self.match(SqlBaseParser.GROUP) + self.state = 2118 + self.match(SqlBaseParser.BY) + self.state = 2119 + localctx._groupByClause = self.groupByClause() + localctx.groupingExpressionsWithGroupingAnalytics.append(localctx._groupByClause) + self.state = 2124 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,256,self._ctx) + while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt==1: + self.state = 2120 + self.match(SqlBaseParser.COMMA) + self.state = 2121 + localctx._groupByClause = self.groupByClause() + localctx.groupingExpressionsWithGroupingAnalytics.append(localctx._groupByClause) + self.state = 2126 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,256,self._ctx) + + pass + + elif la_ == 2: + self.enterOuterAlt(localctx, 2) + self.state = 2127 + self.match(SqlBaseParser.GROUP) + self.state = 2128 + self.match(SqlBaseParser.BY) + self.state = 2129 + localctx._expression = self.expression() + localctx.groupingExpressions.append(localctx._expression) + self.state = 2134 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,257,self._ctx) + while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt==1: + self.state = 2130 + self.match(SqlBaseParser.COMMA) + self.state = 2131 + localctx._expression = self.expression() + localctx.groupingExpressions.append(localctx._expression) + self.state = 2136 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,257,self._ctx) + + self.state = 2154 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,259,self._ctx) + if la_ == 1: + self.state = 2137 + self.match(SqlBaseParser.WITH) + self.state = 2138 + localctx.kind = self.match(SqlBaseParser.ROLLUP) + + elif la_ == 2: + self.state = 2139 + self.match(SqlBaseParser.WITH) + self.state = 2140 + localctx.kind = self.match(SqlBaseParser.CUBE) + + elif la_ == 3: + self.state = 2141 + localctx.kind = self.match(SqlBaseParser.GROUPING) + self.state = 2142 + self.match(SqlBaseParser.SETS) + self.state = 2143 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2144 + self.groupingSet() + self.state = 2149 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 2145 + self.match(SqlBaseParser.COMMA) + self.state = 2146 + self.groupingSet() + self.state = 2151 + self._errHandler.sync(self) + _la = self._input.LA(1) + + self.state = 2152 + self.match(SqlBaseParser.RIGHT_PAREN) + + + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class GroupByClauseContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def groupingAnalytics(self): + return self.getTypedRuleContext(SqlBaseParser.GroupingAnalyticsContext,0) + + + def expression(self): + return self.getTypedRuleContext(SqlBaseParser.ExpressionContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_groupByClause + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterGroupByClause" ): + listener.enterGroupByClause(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitGroupByClause" ): + listener.exitGroupByClause(self) + + + + + def groupByClause(self): + + localctx = SqlBaseParser.GroupByClauseContext(self, self._ctx, self.state) + self.enterRule(localctx, 136, self.RULE_groupByClause) + try: + self.state = 2160 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,261,self._ctx) + if la_ == 1: + self.enterOuterAlt(localctx, 1) + self.state = 2158 + self.groupingAnalytics() + pass + + elif la_ == 2: + self.enterOuterAlt(localctx, 2) + self.state = 2159 + self.expression() + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class GroupingAnalyticsContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + + def groupingSet(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.GroupingSetContext) + else: + return self.getTypedRuleContext(SqlBaseParser.GroupingSetContext,i) + + + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + + def ROLLUP(self): + return self.getToken(SqlBaseParser.ROLLUP, 0) + + def CUBE(self): + return self.getToken(SqlBaseParser.CUBE, 0) + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def GROUPING(self): + return self.getToken(SqlBaseParser.GROUPING, 0) + + def SETS(self): + return self.getToken(SqlBaseParser.SETS, 0) + + def groupingElement(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.GroupingElementContext) + else: + return self.getTypedRuleContext(SqlBaseParser.GroupingElementContext,i) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_groupingAnalytics + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterGroupingAnalytics" ): + listener.enterGroupingAnalytics(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitGroupingAnalytics" ): + listener.exitGroupingAnalytics(self) + + + + + def groupingAnalytics(self): + + localctx = SqlBaseParser.GroupingAnalyticsContext(self, self._ctx, self.state) + self.enterRule(localctx, 138, self.RULE_groupingAnalytics) + self._la = 0 # Token type + try: + self.state = 2187 + self._errHandler.sync(self) + token = self._input.LA(1) + if token in [55, 226]: + self.enterOuterAlt(localctx, 1) + self.state = 2162 + _la = self._input.LA(1) + if not(_la==55 or _la==226): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 2163 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2164 + self.groupingSet() + self.state = 2169 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 2165 + self.match(SqlBaseParser.COMMA) + self.state = 2166 + self.groupingSet() + self.state = 2171 + self._errHandler.sync(self) + _la = self._input.LA(1) + + self.state = 2172 + self.match(SqlBaseParser.RIGHT_PAREN) + pass + elif token in [115]: + self.enterOuterAlt(localctx, 2) + self.state = 2174 + self.match(SqlBaseParser.GROUPING) + self.state = 2175 + self.match(SqlBaseParser.SETS) + self.state = 2176 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2177 + self.groupingElement() + self.state = 2182 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 2178 + self.match(SqlBaseParser.COMMA) + self.state = 2179 + self.groupingElement() + self.state = 2184 + self._errHandler.sync(self) + _la = self._input.LA(1) + + self.state = 2185 + self.match(SqlBaseParser.RIGHT_PAREN) + pass + else: + raise NoViableAltException(self) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class GroupingElementContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def groupingAnalytics(self): + return self.getTypedRuleContext(SqlBaseParser.GroupingAnalyticsContext,0) + + + def groupingSet(self): + return self.getTypedRuleContext(SqlBaseParser.GroupingSetContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_groupingElement + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterGroupingElement" ): + listener.enterGroupingElement(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitGroupingElement" ): + listener.exitGroupingElement(self) + + + + + def groupingElement(self): + + localctx = SqlBaseParser.GroupingElementContext(self, self._ctx, self.state) + self.enterRule(localctx, 140, self.RULE_groupingElement) + try: + self.state = 2191 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,265,self._ctx) + if la_ == 1: + self.enterOuterAlt(localctx, 1) + self.state = 2189 + self.groupingAnalytics() + pass + + elif la_ == 2: + self.enterOuterAlt(localctx, 2) + self.state = 2190 + self.groupingSet() + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class GroupingSetContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + + def expression(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.ExpressionContext) + else: + return self.getTypedRuleContext(SqlBaseParser.ExpressionContext,i) + + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def getRuleIndex(self): + return SqlBaseParser.RULE_groupingSet + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterGroupingSet" ): + listener.enterGroupingSet(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitGroupingSet" ): + listener.exitGroupingSet(self) + + + + + def groupingSet(self): + + localctx = SqlBaseParser.GroupingSetContext(self, self._ctx, self.state) + self.enterRule(localctx, 142, self.RULE_groupingSet) + self._la = 0 # Token type + try: + self.state = 2206 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,268,self._ctx) + if la_ == 1: + self.enterOuterAlt(localctx, 1) + self.state = 2193 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2202 + self._errHandler.sync(self) + _la = self._input.LA(1) + if (((_la) & ~0x3f) == 0 and ((1 << _la) & -252) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & -1) != 0) or ((((_la - 128)) & ~0x3f) == 0 and ((1 << (_la - 128)) & -1) != 0) or ((((_la - 192)) & ~0x3f) == 0 and ((1 << (_la - 192)) & -1) != 0) or ((((_la - 256)) & ~0x3f) == 0 and ((1 << (_la - 256)) & 8074954131875299327) != 0) or ((((_la - 321)) & ~0x3f) == 0 and ((1 << (_la - 321)) & 4193825) != 0): + self.state = 2194 + self.expression() + self.state = 2199 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 2195 + self.match(SqlBaseParser.COMMA) + self.state = 2196 + self.expression() + self.state = 2201 + self._errHandler.sync(self) + _la = self._input.LA(1) + + + + self.state = 2204 + self.match(SqlBaseParser.RIGHT_PAREN) + pass + + elif la_ == 2: + self.enterOuterAlt(localctx, 2) + self.state = 2205 + self.expression() + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class PivotClauseContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self.aggregates = None # NamedExpressionSeqContext + self._pivotValue = None # PivotValueContext + self.pivotValues = list() # of PivotValueContexts + + def PIVOT(self): + return self.getToken(SqlBaseParser.PIVOT, 0) + + def LEFT_PAREN(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.LEFT_PAREN) + else: + return self.getToken(SqlBaseParser.LEFT_PAREN, i) + + def FOR(self): + return self.getToken(SqlBaseParser.FOR, 0) + + def pivotColumn(self): + return self.getTypedRuleContext(SqlBaseParser.PivotColumnContext,0) + + + def IN(self): + return self.getToken(SqlBaseParser.IN, 0) + + def RIGHT_PAREN(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.RIGHT_PAREN) + else: + return self.getToken(SqlBaseParser.RIGHT_PAREN, i) + + def namedExpressionSeq(self): + return self.getTypedRuleContext(SqlBaseParser.NamedExpressionSeqContext,0) + + + def pivotValue(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.PivotValueContext) + else: + return self.getTypedRuleContext(SqlBaseParser.PivotValueContext,i) + + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def getRuleIndex(self): + return SqlBaseParser.RULE_pivotClause + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterPivotClause" ): + listener.enterPivotClause(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitPivotClause" ): + listener.exitPivotClause(self) + + + + + def pivotClause(self): + + localctx = SqlBaseParser.PivotClauseContext(self, self._ctx, self.state) + self.enterRule(localctx, 144, self.RULE_pivotClause) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 2208 + self.match(SqlBaseParser.PIVOT) + self.state = 2209 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2210 + localctx.aggregates = self.namedExpressionSeq() + self.state = 2211 + self.match(SqlBaseParser.FOR) + self.state = 2212 + self.pivotColumn() + self.state = 2213 + self.match(SqlBaseParser.IN) + self.state = 2214 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2215 + localctx._pivotValue = self.pivotValue() + localctx.pivotValues.append(localctx._pivotValue) + self.state = 2220 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 2216 + self.match(SqlBaseParser.COMMA) + self.state = 2217 + localctx._pivotValue = self.pivotValue() + localctx.pivotValues.append(localctx._pivotValue) + self.state = 2222 + self._errHandler.sync(self) + _la = self._input.LA(1) + + self.state = 2223 + self.match(SqlBaseParser.RIGHT_PAREN) + self.state = 2224 + self.match(SqlBaseParser.RIGHT_PAREN) + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class PivotColumnContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self._identifier = None # IdentifierContext + self.identifiers = list() # of IdentifierContexts + + def identifier(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.IdentifierContext) + else: + return self.getTypedRuleContext(SqlBaseParser.IdentifierContext,i) + + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def getRuleIndex(self): + return SqlBaseParser.RULE_pivotColumn + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterPivotColumn" ): + listener.enterPivotColumn(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitPivotColumn" ): + listener.exitPivotColumn(self) + + + + + def pivotColumn(self): + + localctx = SqlBaseParser.PivotColumnContext(self, self._ctx, self.state) + self.enterRule(localctx, 146, self.RULE_pivotColumn) + self._la = 0 # Token type + try: + self.state = 2238 + self._errHandler.sync(self) + token = self._input.LA(1) + if token in [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 331, 341, 342]: + self.enterOuterAlt(localctx, 1) + self.state = 2226 + localctx._identifier = self.identifier() + localctx.identifiers.append(localctx._identifier) + pass + elif token in [2]: + self.enterOuterAlt(localctx, 2) + self.state = 2227 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2228 + localctx._identifier = self.identifier() + localctx.identifiers.append(localctx._identifier) + self.state = 2233 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 2229 + self.match(SqlBaseParser.COMMA) + self.state = 2230 + localctx._identifier = self.identifier() + localctx.identifiers.append(localctx._identifier) + self.state = 2235 + self._errHandler.sync(self) + _la = self._input.LA(1) + + self.state = 2236 + self.match(SqlBaseParser.RIGHT_PAREN) + pass + else: + raise NoViableAltException(self) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class PivotValueContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def expression(self): + return self.getTypedRuleContext(SqlBaseParser.ExpressionContext,0) + + + def identifier(self): + return self.getTypedRuleContext(SqlBaseParser.IdentifierContext,0) + + + def AS(self): + return self.getToken(SqlBaseParser.AS, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_pivotValue + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterPivotValue" ): + listener.enterPivotValue(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitPivotValue" ): + listener.exitPivotValue(self) + + + + + def pivotValue(self): + + localctx = SqlBaseParser.PivotValueContext(self, self._ctx, self.state) + self.enterRule(localctx, 148, self.RULE_pivotValue) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 2240 + self.expression() + self.state = 2245 + self._errHandler.sync(self) + _la = self._input.LA(1) + if (((_la) & ~0x3f) == 0 and ((1 << _la) & -256) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & -1) != 0) or ((((_la - 128)) & ~0x3f) == 0 and ((1 << (_la - 128)) & -1) != 0) or ((((_la - 192)) & ~0x3f) == 0 and ((1 << (_la - 192)) & -1) != 0) or ((((_la - 256)) & ~0x3f) == 0 and ((1 << (_la - 256)) & 4503599627370495) != 0) or ((((_la - 331)) & ~0x3f) == 0 and ((1 << (_la - 331)) & 3073) != 0): + self.state = 2242 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,272,self._ctx) + if la_ == 1: + self.state = 2241 + self.match(SqlBaseParser.AS) + + + self.state = 2244 + self.identifier() + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class UnpivotClauseContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self.nullOperator = None # UnpivotNullClauseContext + self.operator = None # UnpivotOperatorContext + + def UNPIVOT(self): + return self.getToken(SqlBaseParser.UNPIVOT, 0) + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + + def unpivotOperator(self): + return self.getTypedRuleContext(SqlBaseParser.UnpivotOperatorContext,0) + + + def identifier(self): + return self.getTypedRuleContext(SqlBaseParser.IdentifierContext,0) + + + def unpivotNullClause(self): + return self.getTypedRuleContext(SqlBaseParser.UnpivotNullClauseContext,0) + + + def AS(self): + return self.getToken(SqlBaseParser.AS, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_unpivotClause + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterUnpivotClause" ): + listener.enterUnpivotClause(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitUnpivotClause" ): + listener.exitUnpivotClause(self) + + + + + def unpivotClause(self): + + localctx = SqlBaseParser.UnpivotClauseContext(self, self._ctx, self.state) + self.enterRule(localctx, 150, self.RULE_unpivotClause) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 2247 + self.match(SqlBaseParser.UNPIVOT) + self.state = 2249 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==89 or _la==123: + self.state = 2248 + localctx.nullOperator = self.unpivotNullClause() + + + self.state = 2251 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2252 + localctx.operator = self.unpivotOperator() + self.state = 2253 + self.match(SqlBaseParser.RIGHT_PAREN) + self.state = 2258 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,276,self._ctx) + if la_ == 1: + self.state = 2255 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,275,self._ctx) + if la_ == 1: + self.state = 2254 + self.match(SqlBaseParser.AS) + + + self.state = 2257 + self.identifier() + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class UnpivotNullClauseContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def NULLS(self): + return self.getToken(SqlBaseParser.NULLS, 0) + + def INCLUDE(self): + return self.getToken(SqlBaseParser.INCLUDE, 0) + + def EXCLUDE(self): + return self.getToken(SqlBaseParser.EXCLUDE, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_unpivotNullClause + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterUnpivotNullClause" ): + listener.enterUnpivotNullClause(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitUnpivotNullClause" ): + listener.exitUnpivotNullClause(self) + + + + + def unpivotNullClause(self): + + localctx = SqlBaseParser.UnpivotNullClauseContext(self, self._ctx, self.state) + self.enterRule(localctx, 152, self.RULE_unpivotNullClause) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 2260 + _la = self._input.LA(1) + if not(_la==89 or _la==123): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 2261 + self.match(SqlBaseParser.NULLS) + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class UnpivotOperatorContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def unpivotSingleValueColumnClause(self): + return self.getTypedRuleContext(SqlBaseParser.UnpivotSingleValueColumnClauseContext,0) + + + def unpivotMultiValueColumnClause(self): + return self.getTypedRuleContext(SqlBaseParser.UnpivotMultiValueColumnClauseContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_unpivotOperator + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterUnpivotOperator" ): + listener.enterUnpivotOperator(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitUnpivotOperator" ): + listener.exitUnpivotOperator(self) + + + + + def unpivotOperator(self): + + localctx = SqlBaseParser.UnpivotOperatorContext(self, self._ctx, self.state) + self.enterRule(localctx, 154, self.RULE_unpivotOperator) + try: + self.enterOuterAlt(localctx, 1) + self.state = 2265 + self._errHandler.sync(self) + token = self._input.LA(1) + if token in [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 331, 341, 342]: + self.state = 2263 + self.unpivotSingleValueColumnClause() + pass + elif token in [2]: + self.state = 2264 + self.unpivotMultiValueColumnClause() + pass + else: + raise NoViableAltException(self) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class UnpivotSingleValueColumnClauseContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self._unpivotColumnAndAlias = None # UnpivotColumnAndAliasContext + self.unpivotColumns = list() # of UnpivotColumnAndAliasContexts + + def unpivotValueColumn(self): + return self.getTypedRuleContext(SqlBaseParser.UnpivotValueColumnContext,0) + + + def FOR(self): + return self.getToken(SqlBaseParser.FOR, 0) + + def unpivotNameColumn(self): + return self.getTypedRuleContext(SqlBaseParser.UnpivotNameColumnContext,0) + + + def IN(self): + return self.getToken(SqlBaseParser.IN, 0) + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + + def unpivotColumnAndAlias(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.UnpivotColumnAndAliasContext) + else: + return self.getTypedRuleContext(SqlBaseParser.UnpivotColumnAndAliasContext,i) + + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def getRuleIndex(self): + return SqlBaseParser.RULE_unpivotSingleValueColumnClause + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterUnpivotSingleValueColumnClause" ): + listener.enterUnpivotSingleValueColumnClause(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitUnpivotSingleValueColumnClause" ): + listener.exitUnpivotSingleValueColumnClause(self) + + + + + def unpivotSingleValueColumnClause(self): + + localctx = SqlBaseParser.UnpivotSingleValueColumnClauseContext(self, self._ctx, self.state) + self.enterRule(localctx, 156, self.RULE_unpivotSingleValueColumnClause) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 2267 + self.unpivotValueColumn() + self.state = 2268 + self.match(SqlBaseParser.FOR) + self.state = 2269 + self.unpivotNameColumn() + self.state = 2270 + self.match(SqlBaseParser.IN) + self.state = 2271 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2272 + localctx._unpivotColumnAndAlias = self.unpivotColumnAndAlias() + localctx.unpivotColumns.append(localctx._unpivotColumnAndAlias) + self.state = 2277 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 2273 + self.match(SqlBaseParser.COMMA) + self.state = 2274 + localctx._unpivotColumnAndAlias = self.unpivotColumnAndAlias() + localctx.unpivotColumns.append(localctx._unpivotColumnAndAlias) + self.state = 2279 + self._errHandler.sync(self) + _la = self._input.LA(1) + + self.state = 2280 + self.match(SqlBaseParser.RIGHT_PAREN) + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class UnpivotMultiValueColumnClauseContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self._unpivotValueColumn = None # UnpivotValueColumnContext + self.unpivotValueColumns = list() # of UnpivotValueColumnContexts + self._unpivotColumnSet = None # UnpivotColumnSetContext + self.unpivotColumnSets = list() # of UnpivotColumnSetContexts + + def LEFT_PAREN(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.LEFT_PAREN) + else: + return self.getToken(SqlBaseParser.LEFT_PAREN, i) + + def RIGHT_PAREN(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.RIGHT_PAREN) + else: + return self.getToken(SqlBaseParser.RIGHT_PAREN, i) + + def FOR(self): + return self.getToken(SqlBaseParser.FOR, 0) + + def unpivotNameColumn(self): + return self.getTypedRuleContext(SqlBaseParser.UnpivotNameColumnContext,0) + + + def IN(self): + return self.getToken(SqlBaseParser.IN, 0) + + def unpivotValueColumn(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.UnpivotValueColumnContext) + else: + return self.getTypedRuleContext(SqlBaseParser.UnpivotValueColumnContext,i) + + + def unpivotColumnSet(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.UnpivotColumnSetContext) + else: + return self.getTypedRuleContext(SqlBaseParser.UnpivotColumnSetContext,i) + + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def getRuleIndex(self): + return SqlBaseParser.RULE_unpivotMultiValueColumnClause + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterUnpivotMultiValueColumnClause" ): + listener.enterUnpivotMultiValueColumnClause(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitUnpivotMultiValueColumnClause" ): + listener.exitUnpivotMultiValueColumnClause(self) + + + + + def unpivotMultiValueColumnClause(self): + + localctx = SqlBaseParser.UnpivotMultiValueColumnClauseContext(self, self._ctx, self.state) + self.enterRule(localctx, 158, self.RULE_unpivotMultiValueColumnClause) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 2282 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2283 + localctx._unpivotValueColumn = self.unpivotValueColumn() + localctx.unpivotValueColumns.append(localctx._unpivotValueColumn) + self.state = 2288 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 2284 + self.match(SqlBaseParser.COMMA) + self.state = 2285 + localctx._unpivotValueColumn = self.unpivotValueColumn() + localctx.unpivotValueColumns.append(localctx._unpivotValueColumn) + self.state = 2290 + self._errHandler.sync(self) + _la = self._input.LA(1) + + self.state = 2291 + self.match(SqlBaseParser.RIGHT_PAREN) + self.state = 2292 + self.match(SqlBaseParser.FOR) + self.state = 2293 + self.unpivotNameColumn() + self.state = 2294 + self.match(SqlBaseParser.IN) + self.state = 2295 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2296 + localctx._unpivotColumnSet = self.unpivotColumnSet() + localctx.unpivotColumnSets.append(localctx._unpivotColumnSet) + self.state = 2301 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 2297 + self.match(SqlBaseParser.COMMA) + self.state = 2298 + localctx._unpivotColumnSet = self.unpivotColumnSet() + localctx.unpivotColumnSets.append(localctx._unpivotColumnSet) + self.state = 2303 + self._errHandler.sync(self) + _la = self._input.LA(1) + + self.state = 2304 + self.match(SqlBaseParser.RIGHT_PAREN) + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class UnpivotColumnSetContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self._unpivotColumn = None # UnpivotColumnContext + self.unpivotColumns = list() # of UnpivotColumnContexts + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + + def unpivotColumn(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.UnpivotColumnContext) + else: + return self.getTypedRuleContext(SqlBaseParser.UnpivotColumnContext,i) + + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def unpivotAlias(self): + return self.getTypedRuleContext(SqlBaseParser.UnpivotAliasContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_unpivotColumnSet + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterUnpivotColumnSet" ): + listener.enterUnpivotColumnSet(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitUnpivotColumnSet" ): + listener.exitUnpivotColumnSet(self) + + + + + def unpivotColumnSet(self): + + localctx = SqlBaseParser.UnpivotColumnSetContext(self, self._ctx, self.state) + self.enterRule(localctx, 160, self.RULE_unpivotColumnSet) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 2306 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2307 + localctx._unpivotColumn = self.unpivotColumn() + localctx.unpivotColumns.append(localctx._unpivotColumn) + self.state = 2312 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 2308 + self.match(SqlBaseParser.COMMA) + self.state = 2309 + localctx._unpivotColumn = self.unpivotColumn() + localctx.unpivotColumns.append(localctx._unpivotColumn) + self.state = 2314 + self._errHandler.sync(self) + _la = self._input.LA(1) + + self.state = 2315 + self.match(SqlBaseParser.RIGHT_PAREN) + self.state = 2317 + self._errHandler.sync(self) + _la = self._input.LA(1) + if (((_la) & ~0x3f) == 0 and ((1 << _la) & -256) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & -1) != 0) or ((((_la - 128)) & ~0x3f) == 0 and ((1 << (_la - 128)) & -1) != 0) or ((((_la - 192)) & ~0x3f) == 0 and ((1 << (_la - 192)) & -1) != 0) or ((((_la - 256)) & ~0x3f) == 0 and ((1 << (_la - 256)) & 4503599627370495) != 0) or ((((_la - 331)) & ~0x3f) == 0 and ((1 << (_la - 331)) & 3073) != 0): + self.state = 2316 + self.unpivotAlias() + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class UnpivotValueColumnContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def identifier(self): + return self.getTypedRuleContext(SqlBaseParser.IdentifierContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_unpivotValueColumn + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterUnpivotValueColumn" ): + listener.enterUnpivotValueColumn(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitUnpivotValueColumn" ): + listener.exitUnpivotValueColumn(self) + + + + + def unpivotValueColumn(self): + + localctx = SqlBaseParser.UnpivotValueColumnContext(self, self._ctx, self.state) + self.enterRule(localctx, 162, self.RULE_unpivotValueColumn) + try: + self.enterOuterAlt(localctx, 1) + self.state = 2319 + self.identifier() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class UnpivotNameColumnContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def identifier(self): + return self.getTypedRuleContext(SqlBaseParser.IdentifierContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_unpivotNameColumn + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterUnpivotNameColumn" ): + listener.enterUnpivotNameColumn(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitUnpivotNameColumn" ): + listener.exitUnpivotNameColumn(self) + + + + + def unpivotNameColumn(self): + + localctx = SqlBaseParser.UnpivotNameColumnContext(self, self._ctx, self.state) + self.enterRule(localctx, 164, self.RULE_unpivotNameColumn) + try: + self.enterOuterAlt(localctx, 1) + self.state = 2321 + self.identifier() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class UnpivotColumnAndAliasContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def unpivotColumn(self): + return self.getTypedRuleContext(SqlBaseParser.UnpivotColumnContext,0) + + + def unpivotAlias(self): + return self.getTypedRuleContext(SqlBaseParser.UnpivotAliasContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_unpivotColumnAndAlias + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterUnpivotColumnAndAlias" ): + listener.enterUnpivotColumnAndAlias(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitUnpivotColumnAndAlias" ): + listener.exitUnpivotColumnAndAlias(self) + + + + + def unpivotColumnAndAlias(self): + + localctx = SqlBaseParser.UnpivotColumnAndAliasContext(self, self._ctx, self.state) + self.enterRule(localctx, 166, self.RULE_unpivotColumnAndAlias) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 2323 + self.unpivotColumn() + self.state = 2325 + self._errHandler.sync(self) + _la = self._input.LA(1) + if (((_la) & ~0x3f) == 0 and ((1 << _la) & -256) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & -1) != 0) or ((((_la - 128)) & ~0x3f) == 0 and ((1 << (_la - 128)) & -1) != 0) or ((((_la - 192)) & ~0x3f) == 0 and ((1 << (_la - 192)) & -1) != 0) or ((((_la - 256)) & ~0x3f) == 0 and ((1 << (_la - 256)) & 4503599627370495) != 0) or ((((_la - 331)) & ~0x3f) == 0 and ((1 << (_la - 331)) & 3073) != 0): + self.state = 2324 + self.unpivotAlias() + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class UnpivotColumnContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_unpivotColumn + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterUnpivotColumn" ): + listener.enterUnpivotColumn(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitUnpivotColumn" ): + listener.exitUnpivotColumn(self) + + + + + def unpivotColumn(self): + + localctx = SqlBaseParser.UnpivotColumnContext(self, self._ctx, self.state) + self.enterRule(localctx, 168, self.RULE_unpivotColumn) + try: + self.enterOuterAlt(localctx, 1) + self.state = 2327 + self.multipartIdentifier() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class UnpivotAliasContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def identifier(self): + return self.getTypedRuleContext(SqlBaseParser.IdentifierContext,0) + + + def AS(self): + return self.getToken(SqlBaseParser.AS, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_unpivotAlias + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterUnpivotAlias" ): + listener.enterUnpivotAlias(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitUnpivotAlias" ): + listener.exitUnpivotAlias(self) + + + + + def unpivotAlias(self): + + localctx = SqlBaseParser.UnpivotAliasContext(self, self._ctx, self.state) + self.enterRule(localctx, 170, self.RULE_unpivotAlias) + try: + self.enterOuterAlt(localctx, 1) + self.state = 2330 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,284,self._ctx) + if la_ == 1: + self.state = 2329 + self.match(SqlBaseParser.AS) + + + self.state = 2332 + self.identifier() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class LateralViewContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self.tblName = None # IdentifierContext + self._identifier = None # IdentifierContext + self.colName = list() # of IdentifierContexts + + def LATERAL(self): + return self.getToken(SqlBaseParser.LATERAL, 0) + + def VIEW(self): + return self.getToken(SqlBaseParser.VIEW, 0) + + def qualifiedName(self): + return self.getTypedRuleContext(SqlBaseParser.QualifiedNameContext,0) + + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + + def identifier(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.IdentifierContext) + else: + return self.getTypedRuleContext(SqlBaseParser.IdentifierContext,i) + + + def OUTER(self): + return self.getToken(SqlBaseParser.OUTER, 0) + + def expression(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.ExpressionContext) + else: + return self.getTypedRuleContext(SqlBaseParser.ExpressionContext,i) + + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def AS(self): + return self.getToken(SqlBaseParser.AS, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_lateralView + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterLateralView" ): + listener.enterLateralView(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitLateralView" ): + listener.exitLateralView(self) + + + + + def lateralView(self): + + localctx = SqlBaseParser.LateralViewContext(self, self._ctx, self.state) + self.enterRule(localctx, 172, self.RULE_lateralView) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 2334 + self.match(SqlBaseParser.LATERAL) + self.state = 2335 + self.match(SqlBaseParser.VIEW) + self.state = 2337 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,285,self._ctx) + if la_ == 1: + self.state = 2336 + self.match(SqlBaseParser.OUTER) + + + self.state = 2339 + self.qualifiedName() + self.state = 2340 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2349 + self._errHandler.sync(self) + _la = self._input.LA(1) + if (((_la) & ~0x3f) == 0 and ((1 << _la) & -252) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & -1) != 0) or ((((_la - 128)) & ~0x3f) == 0 and ((1 << (_la - 128)) & -1) != 0) or ((((_la - 192)) & ~0x3f) == 0 and ((1 << (_la - 192)) & -1) != 0) or ((((_la - 256)) & ~0x3f) == 0 and ((1 << (_la - 256)) & 8074954131875299327) != 0) or ((((_la - 321)) & ~0x3f) == 0 and ((1 << (_la - 321)) & 4193825) != 0): + self.state = 2341 + self.expression() + self.state = 2346 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 2342 + self.match(SqlBaseParser.COMMA) + self.state = 2343 + self.expression() + self.state = 2348 + self._errHandler.sync(self) + _la = self._input.LA(1) + + + + self.state = 2351 + self.match(SqlBaseParser.RIGHT_PAREN) + self.state = 2352 + localctx.tblName = self.identifier() + self.state = 2364 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,290,self._ctx) + if la_ == 1: + self.state = 2354 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,288,self._ctx) + if la_ == 1: + self.state = 2353 + self.match(SqlBaseParser.AS) + + + self.state = 2356 + localctx._identifier = self.identifier() + localctx.colName.append(localctx._identifier) + self.state = 2361 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,289,self._ctx) + while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt==1: + self.state = 2357 + self.match(SqlBaseParser.COMMA) + self.state = 2358 + localctx._identifier = self.identifier() + localctx.colName.append(localctx._identifier) + self.state = 2363 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,289,self._ctx) + + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class SetQuantifierContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def DISTINCT(self): + return self.getToken(SqlBaseParser.DISTINCT, 0) + + def ALL(self): + return self.getToken(SqlBaseParser.ALL, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_setQuantifier + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSetQuantifier" ): + listener.enterSetQuantifier(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSetQuantifier" ): + listener.exitSetQuantifier(self) + + + + + def setQuantifier(self): + + localctx = SqlBaseParser.SetQuantifierContext(self, self._ctx, self.state) + self.enterRule(localctx, 174, self.RULE_setQuantifier) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 2366 + _la = self._input.LA(1) + if not(_la==10 or _la==79): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class RelationContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def relationPrimary(self): + return self.getTypedRuleContext(SqlBaseParser.RelationPrimaryContext,0) + + + def LATERAL(self): + return self.getToken(SqlBaseParser.LATERAL, 0) + + def relationExtension(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.RelationExtensionContext) + else: + return self.getTypedRuleContext(SqlBaseParser.RelationExtensionContext,i) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_relation + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterRelation" ): + listener.enterRelation(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitRelation" ): + listener.exitRelation(self) + + + + + def relation(self): + + localctx = SqlBaseParser.RelationContext(self, self._ctx, self.state) + self.enterRule(localctx, 176, self.RULE_relation) + try: + self.enterOuterAlt(localctx, 1) + self.state = 2369 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,291,self._ctx) + if la_ == 1: + self.state = 2368 + self.match(SqlBaseParser.LATERAL) + + + self.state = 2371 + self.relationPrimary() + self.state = 2375 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,292,self._ctx) + while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt==1: + self.state = 2372 + self.relationExtension() + self.state = 2377 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,292,self._ctx) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class RelationExtensionContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def joinRelation(self): + return self.getTypedRuleContext(SqlBaseParser.JoinRelationContext,0) + + + def pivotClause(self): + return self.getTypedRuleContext(SqlBaseParser.PivotClauseContext,0) + + + def unpivotClause(self): + return self.getTypedRuleContext(SqlBaseParser.UnpivotClauseContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_relationExtension + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterRelationExtension" ): + listener.enterRelationExtension(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitRelationExtension" ): + listener.exitRelationExtension(self) + + + + + def relationExtension(self): + + localctx = SqlBaseParser.RelationExtensionContext(self, self._ctx, self.state) + self.enterRule(localctx, 178, self.RULE_relationExtension) + try: + self.state = 2381 + self._errHandler.sync(self) + token = self._input.LA(1) + if token in [15, 54, 108, 126, 135, 141, 170, 221, 234]: + self.enterOuterAlt(localctx, 1) + self.state = 2378 + self.joinRelation() + pass + elif token in [196]: + self.enterOuterAlt(localctx, 2) + self.state = 2379 + self.pivotClause() + pass + elif token in [288]: + self.enterOuterAlt(localctx, 3) + self.state = 2380 + self.unpivotClause() + pass + else: + raise NoViableAltException(self) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class JoinRelationContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self.right = None # RelationPrimaryContext + + def JOIN(self): + return self.getToken(SqlBaseParser.JOIN, 0) + + def relationPrimary(self): + return self.getTypedRuleContext(SqlBaseParser.RelationPrimaryContext,0) + + + def joinType(self): + return self.getTypedRuleContext(SqlBaseParser.JoinTypeContext,0) + + + def LATERAL(self): + return self.getToken(SqlBaseParser.LATERAL, 0) + + def joinCriteria(self): + return self.getTypedRuleContext(SqlBaseParser.JoinCriteriaContext,0) + + + def NATURAL(self): + return self.getToken(SqlBaseParser.NATURAL, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_joinRelation + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterJoinRelation" ): + listener.enterJoinRelation(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitJoinRelation" ): + listener.exitJoinRelation(self) + + + + + def joinRelation(self): + + localctx = SqlBaseParser.JoinRelationContext(self, self._ctx, self.state) + self.enterRule(localctx, 180, self.RULE_joinRelation) + try: + self.state = 2400 + self._errHandler.sync(self) + token = self._input.LA(1) + if token in [15, 54, 108, 126, 135, 141, 221, 234]: + self.enterOuterAlt(localctx, 1) + self.state = 2383 + self.joinType() + self.state = 2384 + self.match(SqlBaseParser.JOIN) + self.state = 2386 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,294,self._ctx) + if la_ == 1: + self.state = 2385 + self.match(SqlBaseParser.LATERAL) + + + self.state = 2388 + localctx.right = self.relationPrimary() + self.state = 2390 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,295,self._ctx) + if la_ == 1: + self.state = 2389 + self.joinCriteria() + + + pass + elif token in [170]: + self.enterOuterAlt(localctx, 2) + self.state = 2392 + self.match(SqlBaseParser.NATURAL) + self.state = 2393 + self.joinType() + self.state = 2394 + self.match(SqlBaseParser.JOIN) + self.state = 2396 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,296,self._ctx) + if la_ == 1: + self.state = 2395 + self.match(SqlBaseParser.LATERAL) + + + self.state = 2398 + localctx.right = self.relationPrimary() + pass + else: + raise NoViableAltException(self) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class JoinTypeContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def INNER(self): + return self.getToken(SqlBaseParser.INNER, 0) + + def CROSS(self): + return self.getToken(SqlBaseParser.CROSS, 0) + + def LEFT(self): + return self.getToken(SqlBaseParser.LEFT, 0) + + def OUTER(self): + return self.getToken(SqlBaseParser.OUTER, 0) + + def SEMI(self): + return self.getToken(SqlBaseParser.SEMI, 0) + + def RIGHT(self): + return self.getToken(SqlBaseParser.RIGHT, 0) + + def FULL(self): + return self.getToken(SqlBaseParser.FULL, 0) + + def ANTI(self): + return self.getToken(SqlBaseParser.ANTI, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_joinType + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterJoinType" ): + listener.enterJoinType(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitJoinType" ): + listener.exitJoinType(self) + + + + + def joinType(self): + + localctx = SqlBaseParser.JoinTypeContext(self, self._ctx, self.state) + self.enterRule(localctx, 182, self.RULE_joinType) + self._la = 0 # Token type + try: + self.state = 2426 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,304,self._ctx) + if la_ == 1: + self.enterOuterAlt(localctx, 1) + self.state = 2403 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==126: + self.state = 2402 + self.match(SqlBaseParser.INNER) + + + pass + + elif la_ == 2: + self.enterOuterAlt(localctx, 2) + self.state = 2405 + self.match(SqlBaseParser.CROSS) + pass + + elif la_ == 3: + self.enterOuterAlt(localctx, 3) + self.state = 2406 + self.match(SqlBaseParser.LEFT) + self.state = 2408 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==184: + self.state = 2407 + self.match(SqlBaseParser.OUTER) + + + pass + + elif la_ == 4: + self.enterOuterAlt(localctx, 4) + self.state = 2411 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==141: + self.state = 2410 + self.match(SqlBaseParser.LEFT) + + + self.state = 2413 + self.match(SqlBaseParser.SEMI) + pass + + elif la_ == 5: + self.enterOuterAlt(localctx, 5) + self.state = 2414 + self.match(SqlBaseParser.RIGHT) + self.state = 2416 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==184: + self.state = 2415 + self.match(SqlBaseParser.OUTER) + + + pass + + elif la_ == 6: + self.enterOuterAlt(localctx, 6) + self.state = 2418 + self.match(SqlBaseParser.FULL) + self.state = 2420 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==184: + self.state = 2419 + self.match(SqlBaseParser.OUTER) + + + pass + + elif la_ == 7: + self.enterOuterAlt(localctx, 7) + self.state = 2423 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==141: + self.state = 2422 + self.match(SqlBaseParser.LEFT) + + + self.state = 2425 + self.match(SqlBaseParser.ANTI) + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class JoinCriteriaContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def ON(self): + return self.getToken(SqlBaseParser.ON, 0) + + def booleanExpression(self): + return self.getTypedRuleContext(SqlBaseParser.BooleanExpressionContext,0) + + + def USING(self): + return self.getToken(SqlBaseParser.USING, 0) + + def identifierList(self): + return self.getTypedRuleContext(SqlBaseParser.IdentifierListContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_joinCriteria + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterJoinCriteria" ): + listener.enterJoinCriteria(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitJoinCriteria" ): + listener.exitJoinCriteria(self) + + + + + def joinCriteria(self): + + localctx = SqlBaseParser.JoinCriteriaContext(self, self._ctx, self.state) + self.enterRule(localctx, 184, self.RULE_joinCriteria) + try: + self.state = 2432 + self._errHandler.sync(self) + token = self._input.LA(1) + if token in [177]: + self.enterOuterAlt(localctx, 1) + self.state = 2428 + self.match(SqlBaseParser.ON) + self.state = 2429 + self.booleanExpression(0) + pass + elif token in [293]: + self.enterOuterAlt(localctx, 2) + self.state = 2430 + self.match(SqlBaseParser.USING) + self.state = 2431 + self.identifierList() + pass + else: + raise NoViableAltException(self) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class SampleContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self.seed = None # Token + + def TABLESAMPLE(self): + return self.getToken(SqlBaseParser.TABLESAMPLE, 0) + + def LEFT_PAREN(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.LEFT_PAREN) + else: + return self.getToken(SqlBaseParser.LEFT_PAREN, i) + + def RIGHT_PAREN(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.RIGHT_PAREN) + else: + return self.getToken(SqlBaseParser.RIGHT_PAREN, i) + + def sampleMethod(self): + return self.getTypedRuleContext(SqlBaseParser.SampleMethodContext,0) + + + def REPEATABLE(self): + return self.getToken(SqlBaseParser.REPEATABLE, 0) + + def INTEGER_VALUE(self): + return self.getToken(SqlBaseParser.INTEGER_VALUE, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_sample + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSample" ): + listener.enterSample(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSample" ): + listener.exitSample(self) + + + + + def sample(self): + + localctx = SqlBaseParser.SampleContext(self, self._ctx, self.state) + self.enterRule(localctx, 186, self.RULE_sample) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 2434 + self.match(SqlBaseParser.TABLESAMPLE) + self.state = 2435 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2437 + self._errHandler.sync(self) + _la = self._input.LA(1) + if (((_la) & ~0x3f) == 0 and ((1 << _la) & -252) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & -1) != 0) or ((((_la - 128)) & ~0x3f) == 0 and ((1 << (_la - 128)) & -1) != 0) or ((((_la - 192)) & ~0x3f) == 0 and ((1 << (_la - 192)) & -1) != 0) or ((((_la - 256)) & ~0x3f) == 0 and ((1 << (_la - 256)) & 8074954131875299327) != 0) or ((((_la - 321)) & ~0x3f) == 0 and ((1 << (_la - 321)) & 4193825) != 0): + self.state = 2436 + self.sampleMethod() + + + self.state = 2439 + self.match(SqlBaseParser.RIGHT_PAREN) + self.state = 2444 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,307,self._ctx) + if la_ == 1: + self.state = 2440 + self.match(SqlBaseParser.REPEATABLE) + self.state = 2441 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2442 + localctx.seed = self.match(SqlBaseParser.INTEGER_VALUE) + self.state = 2443 + self.match(SqlBaseParser.RIGHT_PAREN) + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class SampleMethodContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + + def getRuleIndex(self): + return SqlBaseParser.RULE_sampleMethod + + + def copyFrom(self, ctx:ParserRuleContext): + super().copyFrom(ctx) + + + + class SampleByRowsContext(SampleMethodContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.SampleMethodContext + super().__init__(parser) + self.copyFrom(ctx) + + def expression(self): + return self.getTypedRuleContext(SqlBaseParser.ExpressionContext,0) + + def ROWS(self): + return self.getToken(SqlBaseParser.ROWS, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSampleByRows" ): + listener.enterSampleByRows(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSampleByRows" ): + listener.exitSampleByRows(self) + + + class SampleByPercentileContext(SampleMethodContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.SampleMethodContext + super().__init__(parser) + self.negativeSign = None # Token + self.percentage = None # Token + self.copyFrom(ctx) + + def PERCENTLIT(self): + return self.getToken(SqlBaseParser.PERCENTLIT, 0) + def INTEGER_VALUE(self): + return self.getToken(SqlBaseParser.INTEGER_VALUE, 0) + def DECIMAL_VALUE(self): + return self.getToken(SqlBaseParser.DECIMAL_VALUE, 0) + def MINUS(self): + return self.getToken(SqlBaseParser.MINUS, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSampleByPercentile" ): + listener.enterSampleByPercentile(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSampleByPercentile" ): + listener.exitSampleByPercentile(self) + + + class SampleByBucketContext(SampleMethodContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.SampleMethodContext + super().__init__(parser) + self.sampleType = None # Token + self.numerator = None # Token + self.denominator = None # Token + self.copyFrom(ctx) + + def OUT(self): + return self.getToken(SqlBaseParser.OUT, 0) + def OF(self): + return self.getToken(SqlBaseParser.OF, 0) + def BUCKET(self): + return self.getToken(SqlBaseParser.BUCKET, 0) + def INTEGER_VALUE(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.INTEGER_VALUE) + else: + return self.getToken(SqlBaseParser.INTEGER_VALUE, i) + def ON(self): + return self.getToken(SqlBaseParser.ON, 0) + def identifier(self): + return self.getTypedRuleContext(SqlBaseParser.IdentifierContext,0) + + def qualifiedName(self): + return self.getTypedRuleContext(SqlBaseParser.QualifiedNameContext,0) + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSampleByBucket" ): + listener.enterSampleByBucket(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSampleByBucket" ): + listener.exitSampleByBucket(self) + + + class SampleByBytesContext(SampleMethodContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.SampleMethodContext + super().__init__(parser) + self.bytes = None # ExpressionContext + self.copyFrom(ctx) + + def expression(self): + return self.getTypedRuleContext(SqlBaseParser.ExpressionContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSampleByBytes" ): + listener.enterSampleByBytes(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSampleByBytes" ): + listener.exitSampleByBytes(self) + + + + def sampleMethod(self): + + localctx = SqlBaseParser.SampleMethodContext(self, self._ctx, self.state) + self.enterRule(localctx, 188, self.RULE_sampleMethod) + self._la = 0 # Token type + try: + self.state = 2470 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,311,self._ctx) + if la_ == 1: + localctx = SqlBaseParser.SampleByPercentileContext(self, localctx) + self.enterOuterAlt(localctx, 1) + self.state = 2447 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==317: + self.state = 2446 + localctx.negativeSign = self.match(SqlBaseParser.MINUS) + + + self.state = 2449 + localctx.percentage = self._input.LT(1) + _la = self._input.LA(1) + if not(_la==335 or _la==337): + localctx.percentage = self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 2450 + self.match(SqlBaseParser.PERCENTLIT) + pass + + elif la_ == 2: + localctx = SqlBaseParser.SampleByRowsContext(self, localctx) + self.enterOuterAlt(localctx, 2) + self.state = 2451 + self.expression() + self.state = 2452 + self.match(SqlBaseParser.ROWS) + pass + + elif la_ == 3: + localctx = SqlBaseParser.SampleByBucketContext(self, localctx) + self.enterOuterAlt(localctx, 3) + self.state = 2454 + localctx.sampleType = self.match(SqlBaseParser.BUCKET) + self.state = 2455 + localctx.numerator = self.match(SqlBaseParser.INTEGER_VALUE) + self.state = 2456 + self.match(SqlBaseParser.OUT) + self.state = 2457 + self.match(SqlBaseParser.OF) + self.state = 2458 + localctx.denominator = self.match(SqlBaseParser.INTEGER_VALUE) + self.state = 2467 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==177: + self.state = 2459 + self.match(SqlBaseParser.ON) + self.state = 2465 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,309,self._ctx) + if la_ == 1: + self.state = 2460 + self.identifier() + pass + + elif la_ == 2: + self.state = 2461 + self.qualifiedName() + self.state = 2462 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2463 + self.match(SqlBaseParser.RIGHT_PAREN) + pass + + + + + pass + + elif la_ == 4: + localctx = SqlBaseParser.SampleByBytesContext(self, localctx) + self.enterOuterAlt(localctx, 4) + self.state = 2469 + localctx.bytes = self.expression() + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class IdentifierListContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + + def identifierSeq(self): + return self.getTypedRuleContext(SqlBaseParser.IdentifierSeqContext,0) + + + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_identifierList + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterIdentifierList" ): + listener.enterIdentifierList(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitIdentifierList" ): + listener.exitIdentifierList(self) + + + + + def identifierList(self): + + localctx = SqlBaseParser.IdentifierListContext(self, self._ctx, self.state) + self.enterRule(localctx, 190, self.RULE_identifierList) + try: + self.enterOuterAlt(localctx, 1) + self.state = 2472 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2473 + self.identifierSeq() + self.state = 2474 + self.match(SqlBaseParser.RIGHT_PAREN) + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class IdentifierSeqContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self._errorCapturingIdentifier = None # ErrorCapturingIdentifierContext + self.ident = list() # of ErrorCapturingIdentifierContexts + + def errorCapturingIdentifier(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.ErrorCapturingIdentifierContext) + else: + return self.getTypedRuleContext(SqlBaseParser.ErrorCapturingIdentifierContext,i) + + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def getRuleIndex(self): + return SqlBaseParser.RULE_identifierSeq + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterIdentifierSeq" ): + listener.enterIdentifierSeq(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitIdentifierSeq" ): + listener.exitIdentifierSeq(self) + + + + + def identifierSeq(self): + + localctx = SqlBaseParser.IdentifierSeqContext(self, self._ctx, self.state) + self.enterRule(localctx, 192, self.RULE_identifierSeq) + try: + self.enterOuterAlt(localctx, 1) + self.state = 2476 + localctx._errorCapturingIdentifier = self.errorCapturingIdentifier() + localctx.ident.append(localctx._errorCapturingIdentifier) + self.state = 2481 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,312,self._ctx) + while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt==1: + self.state = 2477 + self.match(SqlBaseParser.COMMA) + self.state = 2478 + localctx._errorCapturingIdentifier = self.errorCapturingIdentifier() + localctx.ident.append(localctx._errorCapturingIdentifier) + self.state = 2483 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,312,self._ctx) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class OrderedIdentifierListContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + + def orderedIdentifier(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.OrderedIdentifierContext) + else: + return self.getTypedRuleContext(SqlBaseParser.OrderedIdentifierContext,i) + + + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def getRuleIndex(self): + return SqlBaseParser.RULE_orderedIdentifierList + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterOrderedIdentifierList" ): + listener.enterOrderedIdentifierList(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitOrderedIdentifierList" ): + listener.exitOrderedIdentifierList(self) + + + + + def orderedIdentifierList(self): + + localctx = SqlBaseParser.OrderedIdentifierListContext(self, self._ctx, self.state) + self.enterRule(localctx, 194, self.RULE_orderedIdentifierList) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 2484 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2485 + self.orderedIdentifier() + self.state = 2490 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 2486 + self.match(SqlBaseParser.COMMA) + self.state = 2487 + self.orderedIdentifier() + self.state = 2492 + self._errHandler.sync(self) + _la = self._input.LA(1) + + self.state = 2493 + self.match(SqlBaseParser.RIGHT_PAREN) + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class OrderedIdentifierContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self.ident = None # ErrorCapturingIdentifierContext + self.ordering = None # Token + + def errorCapturingIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.ErrorCapturingIdentifierContext,0) + + + def ASC(self): + return self.getToken(SqlBaseParser.ASC, 0) + + def DESC(self): + return self.getToken(SqlBaseParser.DESC, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_orderedIdentifier + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterOrderedIdentifier" ): + listener.enterOrderedIdentifier(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitOrderedIdentifier" ): + listener.exitOrderedIdentifier(self) + + + + + def orderedIdentifier(self): + + localctx = SqlBaseParser.OrderedIdentifierContext(self, self._ctx, self.state) + self.enterRule(localctx, 196, self.RULE_orderedIdentifier) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 2495 + localctx.ident = self.errorCapturingIdentifier() + self.state = 2497 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==21 or _la==74: + self.state = 2496 + localctx.ordering = self._input.LT(1) + _la = self._input.LA(1) + if not(_la==21 or _la==74): + localctx.ordering = self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class IdentifierCommentListContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + + def identifierComment(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.IdentifierCommentContext) + else: + return self.getTypedRuleContext(SqlBaseParser.IdentifierCommentContext,i) + + + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def getRuleIndex(self): + return SqlBaseParser.RULE_identifierCommentList + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterIdentifierCommentList" ): + listener.enterIdentifierCommentList(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitIdentifierCommentList" ): + listener.exitIdentifierCommentList(self) + + + + + def identifierCommentList(self): + + localctx = SqlBaseParser.IdentifierCommentListContext(self, self._ctx, self.state) + self.enterRule(localctx, 198, self.RULE_identifierCommentList) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 2499 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2500 + self.identifierComment() + self.state = 2505 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 2501 + self.match(SqlBaseParser.COMMA) + self.state = 2502 + self.identifierComment() + self.state = 2507 + self._errHandler.sync(self) + _la = self._input.LA(1) + + self.state = 2508 + self.match(SqlBaseParser.RIGHT_PAREN) + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class IdentifierCommentContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def identifier(self): + return self.getTypedRuleContext(SqlBaseParser.IdentifierContext,0) + + + def commentSpec(self): + return self.getTypedRuleContext(SqlBaseParser.CommentSpecContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_identifierComment + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterIdentifierComment" ): + listener.enterIdentifierComment(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitIdentifierComment" ): + listener.exitIdentifierComment(self) + + + + + def identifierComment(self): + + localctx = SqlBaseParser.IdentifierCommentContext(self, self._ctx, self.state) + self.enterRule(localctx, 200, self.RULE_identifierComment) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 2510 + self.identifier() + self.state = 2512 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==45: + self.state = 2511 + self.commentSpec() + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class RelationPrimaryContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + + def getRuleIndex(self): + return SqlBaseParser.RULE_relationPrimary + + + def copyFrom(self, ctx:ParserRuleContext): + super().copyFrom(ctx) + + + + class TableValuedFunctionContext(RelationPrimaryContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.RelationPrimaryContext + super().__init__(parser) + self.copyFrom(ctx) + + def functionTable(self): + return self.getTypedRuleContext(SqlBaseParser.FunctionTableContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterTableValuedFunction" ): + listener.enterTableValuedFunction(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitTableValuedFunction" ): + listener.exitTableValuedFunction(self) + + + class InlineTableDefault2Context(RelationPrimaryContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.RelationPrimaryContext + super().__init__(parser) + self.copyFrom(ctx) + + def inlineTable(self): + return self.getTypedRuleContext(SqlBaseParser.InlineTableContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterInlineTableDefault2" ): + listener.enterInlineTableDefault2(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitInlineTableDefault2" ): + listener.exitInlineTableDefault2(self) + + + class AliasedRelationContext(RelationPrimaryContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.RelationPrimaryContext + super().__init__(parser) + self.copyFrom(ctx) + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + def relation(self): + return self.getTypedRuleContext(SqlBaseParser.RelationContext,0) + + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + def tableAlias(self): + return self.getTypedRuleContext(SqlBaseParser.TableAliasContext,0) + + def sample(self): + return self.getTypedRuleContext(SqlBaseParser.SampleContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterAliasedRelation" ): + listener.enterAliasedRelation(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitAliasedRelation" ): + listener.exitAliasedRelation(self) + + + class AliasedQueryContext(RelationPrimaryContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.RelationPrimaryContext + super().__init__(parser) + self.copyFrom(ctx) + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + def query(self): + return self.getTypedRuleContext(SqlBaseParser.QueryContext,0) + + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + def tableAlias(self): + return self.getTypedRuleContext(SqlBaseParser.TableAliasContext,0) + + def sample(self): + return self.getTypedRuleContext(SqlBaseParser.SampleContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterAliasedQuery" ): + listener.enterAliasedQuery(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitAliasedQuery" ): + listener.exitAliasedQuery(self) + + + class TableNameContext(RelationPrimaryContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.RelationPrimaryContext + super().__init__(parser) + self.copyFrom(ctx) + + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + def tableAlias(self): + return self.getTypedRuleContext(SqlBaseParser.TableAliasContext,0) + + def temporalClause(self): + return self.getTypedRuleContext(SqlBaseParser.TemporalClauseContext,0) + + def sample(self): + return self.getTypedRuleContext(SqlBaseParser.SampleContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterTableName" ): + listener.enterTableName(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitTableName" ): + listener.exitTableName(self) + + + + def relationPrimary(self): + + localctx = SqlBaseParser.RelationPrimaryContext(self, self._ctx, self.state) + self.enterRule(localctx, 202, self.RULE_relationPrimary) + try: + self.state = 2541 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,321,self._ctx) + if la_ == 1: + localctx = SqlBaseParser.TableNameContext(self, localctx) + self.enterOuterAlt(localctx, 1) + self.state = 2514 + self.multipartIdentifier() + self.state = 2516 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,317,self._ctx) + if la_ == 1: + self.state = 2515 + self.temporalClause() + + + self.state = 2519 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,318,self._ctx) + if la_ == 1: + self.state = 2518 + self.sample() + + + self.state = 2521 + self.tableAlias() + pass + + elif la_ == 2: + localctx = SqlBaseParser.AliasedQueryContext(self, localctx) + self.enterOuterAlt(localctx, 2) + self.state = 2523 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2524 + self.query() + self.state = 2525 + self.match(SqlBaseParser.RIGHT_PAREN) + self.state = 2527 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,319,self._ctx) + if la_ == 1: + self.state = 2526 + self.sample() + + + self.state = 2529 + self.tableAlias() + pass + + elif la_ == 3: + localctx = SqlBaseParser.AliasedRelationContext(self, localctx) + self.enterOuterAlt(localctx, 3) + self.state = 2531 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2532 + self.relation() + self.state = 2533 + self.match(SqlBaseParser.RIGHT_PAREN) + self.state = 2535 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,320,self._ctx) + if la_ == 1: + self.state = 2534 + self.sample() + + + self.state = 2537 + self.tableAlias() + pass + + elif la_ == 4: + localctx = SqlBaseParser.InlineTableDefault2Context(self, localctx) + self.enterOuterAlt(localctx, 4) + self.state = 2539 + self.inlineTable() + pass + + elif la_ == 5: + localctx = SqlBaseParser.TableValuedFunctionContext(self, localctx) + self.enterOuterAlt(localctx, 5) + self.state = 2540 + self.functionTable() + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class InlineTableContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def VALUES(self): + return self.getToken(SqlBaseParser.VALUES, 0) + + def expression(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.ExpressionContext) + else: + return self.getTypedRuleContext(SqlBaseParser.ExpressionContext,i) + + + def tableAlias(self): + return self.getTypedRuleContext(SqlBaseParser.TableAliasContext,0) + + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def getRuleIndex(self): + return SqlBaseParser.RULE_inlineTable + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterInlineTable" ): + listener.enterInlineTable(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitInlineTable" ): + listener.exitInlineTable(self) + + + + + def inlineTable(self): + + localctx = SqlBaseParser.InlineTableContext(self, self._ctx, self.state) + self.enterRule(localctx, 204, self.RULE_inlineTable) + try: + self.enterOuterAlt(localctx, 1) + self.state = 2543 + self.match(SqlBaseParser.VALUES) + self.state = 2544 + self.expression() + self.state = 2549 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,322,self._ctx) + while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt==1: + self.state = 2545 + self.match(SqlBaseParser.COMMA) + self.state = 2546 + self.expression() + self.state = 2551 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,322,self._ctx) + + self.state = 2552 + self.tableAlias() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class FunctionTableContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self.funcName = None # FunctionNameContext + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + + def tableAlias(self): + return self.getTypedRuleContext(SqlBaseParser.TableAliasContext,0) + + + def functionName(self): + return self.getTypedRuleContext(SqlBaseParser.FunctionNameContext,0) + + + def expression(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.ExpressionContext) + else: + return self.getTypedRuleContext(SqlBaseParser.ExpressionContext,i) + + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def getRuleIndex(self): + return SqlBaseParser.RULE_functionTable + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterFunctionTable" ): + listener.enterFunctionTable(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitFunctionTable" ): + listener.exitFunctionTable(self) + + + + + def functionTable(self): + + localctx = SqlBaseParser.FunctionTableContext(self, self._ctx, self.state) + self.enterRule(localctx, 206, self.RULE_functionTable) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 2554 + localctx.funcName = self.functionName() + self.state = 2555 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2564 + self._errHandler.sync(self) + _la = self._input.LA(1) + if (((_la) & ~0x3f) == 0 and ((1 << _la) & -252) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & -1) != 0) or ((((_la - 128)) & ~0x3f) == 0 and ((1 << (_la - 128)) & -1) != 0) or ((((_la - 192)) & ~0x3f) == 0 and ((1 << (_la - 192)) & -1) != 0) or ((((_la - 256)) & ~0x3f) == 0 and ((1 << (_la - 256)) & 8074954131875299327) != 0) or ((((_la - 321)) & ~0x3f) == 0 and ((1 << (_la - 321)) & 4193825) != 0): + self.state = 2556 + self.expression() + self.state = 2561 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 2557 + self.match(SqlBaseParser.COMMA) + self.state = 2558 + self.expression() + self.state = 2563 + self._errHandler.sync(self) + _la = self._input.LA(1) + + + + self.state = 2566 + self.match(SqlBaseParser.RIGHT_PAREN) + self.state = 2567 + self.tableAlias() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class TableAliasContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def strictIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.StrictIdentifierContext,0) + + + def AS(self): + return self.getToken(SqlBaseParser.AS, 0) + + def identifierList(self): + return self.getTypedRuleContext(SqlBaseParser.IdentifierListContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_tableAlias + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterTableAlias" ): + listener.enterTableAlias(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitTableAlias" ): + listener.exitTableAlias(self) + + + + + def tableAlias(self): + + localctx = SqlBaseParser.TableAliasContext(self, self._ctx, self.state) + self.enterRule(localctx, 208, self.RULE_tableAlias) + try: + self.enterOuterAlt(localctx, 1) + self.state = 2576 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,327,self._ctx) + if la_ == 1: + self.state = 2570 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,325,self._ctx) + if la_ == 1: + self.state = 2569 + self.match(SqlBaseParser.AS) + + + self.state = 2572 + self.strictIdentifier() + self.state = 2574 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,326,self._ctx) + if la_ == 1: + self.state = 2573 + self.identifierList() + + + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class RowFormatContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + + def getRuleIndex(self): + return SqlBaseParser.RULE_rowFormat + + + def copyFrom(self, ctx:ParserRuleContext): + super().copyFrom(ctx) + + + + class RowFormatSerdeContext(RowFormatContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.RowFormatContext + super().__init__(parser) + self.name = None # StringLitContext + self.props = None # PropertyListContext + self.copyFrom(ctx) + + def ROW(self): + return self.getToken(SqlBaseParser.ROW, 0) + def FORMAT(self): + return self.getToken(SqlBaseParser.FORMAT, 0) + def SERDE(self): + return self.getToken(SqlBaseParser.SERDE, 0) + def stringLit(self): + return self.getTypedRuleContext(SqlBaseParser.StringLitContext,0) + + def WITH(self): + return self.getToken(SqlBaseParser.WITH, 0) + def SERDEPROPERTIES(self): + return self.getToken(SqlBaseParser.SERDEPROPERTIES, 0) + def propertyList(self): + return self.getTypedRuleContext(SqlBaseParser.PropertyListContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterRowFormatSerde" ): + listener.enterRowFormatSerde(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitRowFormatSerde" ): + listener.exitRowFormatSerde(self) + + + class RowFormatDelimitedContext(RowFormatContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.RowFormatContext + super().__init__(parser) + self.fieldsTerminatedBy = None # StringLitContext + self.escapedBy = None # StringLitContext + self.collectionItemsTerminatedBy = None # StringLitContext + self.keysTerminatedBy = None # StringLitContext + self.linesSeparatedBy = None # StringLitContext + self.nullDefinedAs = None # StringLitContext + self.copyFrom(ctx) + + def ROW(self): + return self.getToken(SqlBaseParser.ROW, 0) + def FORMAT(self): + return self.getToken(SqlBaseParser.FORMAT, 0) + def DELIMITED(self): + return self.getToken(SqlBaseParser.DELIMITED, 0) + def FIELDS(self): + return self.getToken(SqlBaseParser.FIELDS, 0) + def TERMINATED(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.TERMINATED) + else: + return self.getToken(SqlBaseParser.TERMINATED, i) + def BY(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.BY) + else: + return self.getToken(SqlBaseParser.BY, i) + def COLLECTION(self): + return self.getToken(SqlBaseParser.COLLECTION, 0) + def ITEMS(self): + return self.getToken(SqlBaseParser.ITEMS, 0) + def MAP(self): + return self.getToken(SqlBaseParser.MAP, 0) + def KEYS(self): + return self.getToken(SqlBaseParser.KEYS, 0) + def LINES(self): + return self.getToken(SqlBaseParser.LINES, 0) + def NULL(self): + return self.getToken(SqlBaseParser.NULL, 0) + def DEFINED(self): + return self.getToken(SqlBaseParser.DEFINED, 0) + def AS(self): + return self.getToken(SqlBaseParser.AS, 0) + def stringLit(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.StringLitContext) + else: + return self.getTypedRuleContext(SqlBaseParser.StringLitContext,i) + + def ESCAPED(self): + return self.getToken(SqlBaseParser.ESCAPED, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterRowFormatDelimited" ): + listener.enterRowFormatDelimited(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitRowFormatDelimited" ): + listener.exitRowFormatDelimited(self) + + + + def rowFormat(self): + + localctx = SqlBaseParser.RowFormatContext(self, self._ctx, self.state) + self.enterRule(localctx, 210, self.RULE_rowFormat) + try: + self.state = 2627 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,335,self._ctx) + if la_ == 1: + localctx = SqlBaseParser.RowFormatSerdeContext(self, localctx) + self.enterOuterAlt(localctx, 1) + self.state = 2578 + self.match(SqlBaseParser.ROW) + self.state = 2579 + self.match(SqlBaseParser.FORMAT) + self.state = 2580 + self.match(SqlBaseParser.SERDE) + self.state = 2581 + localctx.name = self.stringLit() + self.state = 2585 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,328,self._ctx) + if la_ == 1: + self.state = 2582 + self.match(SqlBaseParser.WITH) + self.state = 2583 + self.match(SqlBaseParser.SERDEPROPERTIES) + self.state = 2584 + localctx.props = self.propertyList() + + + pass + + elif la_ == 2: + localctx = SqlBaseParser.RowFormatDelimitedContext(self, localctx) + self.enterOuterAlt(localctx, 2) + self.state = 2587 + self.match(SqlBaseParser.ROW) + self.state = 2588 + self.match(SqlBaseParser.FORMAT) + self.state = 2589 + self.match(SqlBaseParser.DELIMITED) + self.state = 2599 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,330,self._ctx) + if la_ == 1: + self.state = 2590 + self.match(SqlBaseParser.FIELDS) + self.state = 2591 + self.match(SqlBaseParser.TERMINATED) + self.state = 2592 + self.match(SqlBaseParser.BY) + self.state = 2593 + localctx.fieldsTerminatedBy = self.stringLit() + self.state = 2597 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,329,self._ctx) + if la_ == 1: + self.state = 2594 + self.match(SqlBaseParser.ESCAPED) + self.state = 2595 + self.match(SqlBaseParser.BY) + self.state = 2596 + localctx.escapedBy = self.stringLit() + + + + + self.state = 2606 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,331,self._ctx) + if la_ == 1: + self.state = 2601 + self.match(SqlBaseParser.COLLECTION) + self.state = 2602 + self.match(SqlBaseParser.ITEMS) + self.state = 2603 + self.match(SqlBaseParser.TERMINATED) + self.state = 2604 + self.match(SqlBaseParser.BY) + self.state = 2605 + localctx.collectionItemsTerminatedBy = self.stringLit() + + + self.state = 2613 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,332,self._ctx) + if la_ == 1: + self.state = 2608 + self.match(SqlBaseParser.MAP) + self.state = 2609 + self.match(SqlBaseParser.KEYS) + self.state = 2610 + self.match(SqlBaseParser.TERMINATED) + self.state = 2611 + self.match(SqlBaseParser.BY) + self.state = 2612 + localctx.keysTerminatedBy = self.stringLit() + + + self.state = 2619 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,333,self._ctx) + if la_ == 1: + self.state = 2615 + self.match(SqlBaseParser.LINES) + self.state = 2616 + self.match(SqlBaseParser.TERMINATED) + self.state = 2617 + self.match(SqlBaseParser.BY) + self.state = 2618 + localctx.linesSeparatedBy = self.stringLit() + + + self.state = 2625 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,334,self._ctx) + if la_ == 1: + self.state = 2621 + self.match(SqlBaseParser.NULL) + self.state = 2622 + self.match(SqlBaseParser.DEFINED) + self.state = 2623 + self.match(SqlBaseParser.AS) + self.state = 2624 + localctx.nullDefinedAs = self.stringLit() + + + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class MultipartIdentifierListContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def multipartIdentifier(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.MultipartIdentifierContext) + else: + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,i) + + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def getRuleIndex(self): + return SqlBaseParser.RULE_multipartIdentifierList + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterMultipartIdentifierList" ): + listener.enterMultipartIdentifierList(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitMultipartIdentifierList" ): + listener.exitMultipartIdentifierList(self) + + + + + def multipartIdentifierList(self): + + localctx = SqlBaseParser.MultipartIdentifierListContext(self, self._ctx, self.state) + self.enterRule(localctx, 212, self.RULE_multipartIdentifierList) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 2629 + self.multipartIdentifier() + self.state = 2634 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 2630 + self.match(SqlBaseParser.COMMA) + self.state = 2631 + self.multipartIdentifier() + self.state = 2636 + self._errHandler.sync(self) + _la = self._input.LA(1) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class MultipartIdentifierContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self._errorCapturingIdentifier = None # ErrorCapturingIdentifierContext + self.parts = list() # of ErrorCapturingIdentifierContexts + + def errorCapturingIdentifier(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.ErrorCapturingIdentifierContext) + else: + return self.getTypedRuleContext(SqlBaseParser.ErrorCapturingIdentifierContext,i) + + + def DOT(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.DOT) + else: + return self.getToken(SqlBaseParser.DOT, i) + + def getRuleIndex(self): + return SqlBaseParser.RULE_multipartIdentifier + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterMultipartIdentifier" ): + listener.enterMultipartIdentifier(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitMultipartIdentifier" ): + listener.exitMultipartIdentifier(self) + + + + + def multipartIdentifier(self): + + localctx = SqlBaseParser.MultipartIdentifierContext(self, self._ctx, self.state) + self.enterRule(localctx, 214, self.RULE_multipartIdentifier) + try: + self.enterOuterAlt(localctx, 1) + self.state = 2637 + localctx._errorCapturingIdentifier = self.errorCapturingIdentifier() + localctx.parts.append(localctx._errorCapturingIdentifier) + self.state = 2642 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,337,self._ctx) + while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt==1: + self.state = 2638 + self.match(SqlBaseParser.DOT) + self.state = 2639 + localctx._errorCapturingIdentifier = self.errorCapturingIdentifier() + localctx.parts.append(localctx._errorCapturingIdentifier) + self.state = 2644 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,337,self._ctx) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class MultipartIdentifierPropertyListContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def multipartIdentifierProperty(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.MultipartIdentifierPropertyContext) + else: + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierPropertyContext,i) + + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def getRuleIndex(self): + return SqlBaseParser.RULE_multipartIdentifierPropertyList + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterMultipartIdentifierPropertyList" ): + listener.enterMultipartIdentifierPropertyList(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitMultipartIdentifierPropertyList" ): + listener.exitMultipartIdentifierPropertyList(self) + + + + + def multipartIdentifierPropertyList(self): + + localctx = SqlBaseParser.MultipartIdentifierPropertyListContext(self, self._ctx, self.state) + self.enterRule(localctx, 216, self.RULE_multipartIdentifierPropertyList) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 2645 + self.multipartIdentifierProperty() + self.state = 2650 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 2646 + self.match(SqlBaseParser.COMMA) + self.state = 2647 + self.multipartIdentifierProperty() + self.state = 2652 + self._errHandler.sync(self) + _la = self._input.LA(1) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class MultipartIdentifierPropertyContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self.options = None # PropertyListContext + + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + + def OPTIONS(self): + return self.getToken(SqlBaseParser.OPTIONS, 0) + + def propertyList(self): + return self.getTypedRuleContext(SqlBaseParser.PropertyListContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_multipartIdentifierProperty + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterMultipartIdentifierProperty" ): + listener.enterMultipartIdentifierProperty(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitMultipartIdentifierProperty" ): + listener.exitMultipartIdentifierProperty(self) + + + + + def multipartIdentifierProperty(self): + + localctx = SqlBaseParser.MultipartIdentifierPropertyContext(self, self._ctx, self.state) + self.enterRule(localctx, 218, self.RULE_multipartIdentifierProperty) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 2653 + self.multipartIdentifier() + self.state = 2656 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==180: + self.state = 2654 + self.match(SqlBaseParser.OPTIONS) + self.state = 2655 + localctx.options = self.propertyList() + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class TableIdentifierContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self.db = None # ErrorCapturingIdentifierContext + self.table = None # ErrorCapturingIdentifierContext + + def errorCapturingIdentifier(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.ErrorCapturingIdentifierContext) + else: + return self.getTypedRuleContext(SqlBaseParser.ErrorCapturingIdentifierContext,i) + + + def DOT(self): + return self.getToken(SqlBaseParser.DOT, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_tableIdentifier + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterTableIdentifier" ): + listener.enterTableIdentifier(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitTableIdentifier" ): + listener.exitTableIdentifier(self) + + + + + def tableIdentifier(self): + + localctx = SqlBaseParser.TableIdentifierContext(self, self._ctx, self.state) + self.enterRule(localctx, 220, self.RULE_tableIdentifier) + try: + self.enterOuterAlt(localctx, 1) + self.state = 2661 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,340,self._ctx) + if la_ == 1: + self.state = 2658 + localctx.db = self.errorCapturingIdentifier() + self.state = 2659 + self.match(SqlBaseParser.DOT) + + + self.state = 2663 + localctx.table = self.errorCapturingIdentifier() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class FunctionIdentifierContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self.db = None # ErrorCapturingIdentifierContext + self.function = None # ErrorCapturingIdentifierContext + + def errorCapturingIdentifier(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.ErrorCapturingIdentifierContext) + else: + return self.getTypedRuleContext(SqlBaseParser.ErrorCapturingIdentifierContext,i) + + + def DOT(self): + return self.getToken(SqlBaseParser.DOT, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_functionIdentifier + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterFunctionIdentifier" ): + listener.enterFunctionIdentifier(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitFunctionIdentifier" ): + listener.exitFunctionIdentifier(self) + + + + + def functionIdentifier(self): + + localctx = SqlBaseParser.FunctionIdentifierContext(self, self._ctx, self.state) + self.enterRule(localctx, 222, self.RULE_functionIdentifier) + try: + self.enterOuterAlt(localctx, 1) + self.state = 2668 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,341,self._ctx) + if la_ == 1: + self.state = 2665 + localctx.db = self.errorCapturingIdentifier() + self.state = 2666 + self.match(SqlBaseParser.DOT) + + + self.state = 2670 + localctx.function = self.errorCapturingIdentifier() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class NamedExpressionContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self.name = None # ErrorCapturingIdentifierContext + + def expression(self): + return self.getTypedRuleContext(SqlBaseParser.ExpressionContext,0) + + + def identifierList(self): + return self.getTypedRuleContext(SqlBaseParser.IdentifierListContext,0) + + + def AS(self): + return self.getToken(SqlBaseParser.AS, 0) + + def errorCapturingIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.ErrorCapturingIdentifierContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_namedExpression + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterNamedExpression" ): + listener.enterNamedExpression(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitNamedExpression" ): + listener.exitNamedExpression(self) + + + + + def namedExpression(self): + + localctx = SqlBaseParser.NamedExpressionContext(self, self._ctx, self.state) + self.enterRule(localctx, 224, self.RULE_namedExpression) + try: + self.enterOuterAlt(localctx, 1) + self.state = 2672 + self.expression() + self.state = 2680 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,344,self._ctx) + if la_ == 1: + self.state = 2674 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,342,self._ctx) + if la_ == 1: + self.state = 2673 + self.match(SqlBaseParser.AS) + + + self.state = 2678 + self._errHandler.sync(self) + token = self._input.LA(1) + if token in [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 331, 341, 342]: + self.state = 2676 + localctx.name = self.errorCapturingIdentifier() + pass + elif token in [2]: + self.state = 2677 + self.identifierList() + pass + else: + raise NoViableAltException(self) + + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class NamedExpressionSeqContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def namedExpression(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.NamedExpressionContext) + else: + return self.getTypedRuleContext(SqlBaseParser.NamedExpressionContext,i) + + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def getRuleIndex(self): + return SqlBaseParser.RULE_namedExpressionSeq + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterNamedExpressionSeq" ): + listener.enterNamedExpressionSeq(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitNamedExpressionSeq" ): + listener.exitNamedExpressionSeq(self) + + + + + def namedExpressionSeq(self): + + localctx = SqlBaseParser.NamedExpressionSeqContext(self, self._ctx, self.state) + self.enterRule(localctx, 226, self.RULE_namedExpressionSeq) + try: + self.enterOuterAlt(localctx, 1) + self.state = 2682 + self.namedExpression() + self.state = 2687 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,345,self._ctx) + while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt==1: + self.state = 2683 + self.match(SqlBaseParser.COMMA) + self.state = 2684 + self.namedExpression() + self.state = 2689 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,345,self._ctx) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class PartitionFieldListContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self._partitionField = None # PartitionFieldContext + self.fields = list() # of PartitionFieldContexts + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + + def partitionField(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.PartitionFieldContext) + else: + return self.getTypedRuleContext(SqlBaseParser.PartitionFieldContext,i) + + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def getRuleIndex(self): + return SqlBaseParser.RULE_partitionFieldList + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterPartitionFieldList" ): + listener.enterPartitionFieldList(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitPartitionFieldList" ): + listener.exitPartitionFieldList(self) + + + + + def partitionFieldList(self): + + localctx = SqlBaseParser.PartitionFieldListContext(self, self._ctx, self.state) + self.enterRule(localctx, 228, self.RULE_partitionFieldList) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 2690 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2691 + localctx._partitionField = self.partitionField() + localctx.fields.append(localctx._partitionField) + self.state = 2696 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 2692 + self.match(SqlBaseParser.COMMA) + self.state = 2693 + localctx._partitionField = self.partitionField() + localctx.fields.append(localctx._partitionField) + self.state = 2698 + self._errHandler.sync(self) + _la = self._input.LA(1) + + self.state = 2699 + self.match(SqlBaseParser.RIGHT_PAREN) + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class PartitionFieldContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + + def getRuleIndex(self): + return SqlBaseParser.RULE_partitionField + + + def copyFrom(self, ctx:ParserRuleContext): + super().copyFrom(ctx) + + + + class PartitionColumnContext(PartitionFieldContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.PartitionFieldContext + super().__init__(parser) + self.copyFrom(ctx) + + def colType(self): + return self.getTypedRuleContext(SqlBaseParser.ColTypeContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterPartitionColumn" ): + listener.enterPartitionColumn(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitPartitionColumn" ): + listener.exitPartitionColumn(self) + + + class PartitionTransformContext(PartitionFieldContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.PartitionFieldContext + super().__init__(parser) + self.copyFrom(ctx) + + def transform(self): + return self.getTypedRuleContext(SqlBaseParser.TransformContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterPartitionTransform" ): + listener.enterPartitionTransform(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitPartitionTransform" ): + listener.exitPartitionTransform(self) + + + + def partitionField(self): + + localctx = SqlBaseParser.PartitionFieldContext(self, self._ctx, self.state) + self.enterRule(localctx, 230, self.RULE_partitionField) + try: + self.state = 2703 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,347,self._ctx) + if la_ == 1: + localctx = SqlBaseParser.PartitionTransformContext(self, localctx) + self.enterOuterAlt(localctx, 1) + self.state = 2701 + self.transform() + pass + + elif la_ == 2: + localctx = SqlBaseParser.PartitionColumnContext(self, localctx) + self.enterOuterAlt(localctx, 2) + self.state = 2702 + self.colType() + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class TransformContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + + def getRuleIndex(self): + return SqlBaseParser.RULE_transform + + + def copyFrom(self, ctx:ParserRuleContext): + super().copyFrom(ctx) + + + + class IdentityTransformContext(TransformContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.TransformContext + super().__init__(parser) + self.copyFrom(ctx) + + def qualifiedName(self): + return self.getTypedRuleContext(SqlBaseParser.QualifiedNameContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterIdentityTransform" ): + listener.enterIdentityTransform(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitIdentityTransform" ): + listener.exitIdentityTransform(self) + + + class ApplyTransformContext(TransformContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.TransformContext + super().__init__(parser) + self.transformName = None # IdentifierContext + self._transformArgument = None # TransformArgumentContext + self.argument = list() # of TransformArgumentContexts + self.copyFrom(ctx) + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + def identifier(self): + return self.getTypedRuleContext(SqlBaseParser.IdentifierContext,0) + + def transformArgument(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.TransformArgumentContext) + else: + return self.getTypedRuleContext(SqlBaseParser.TransformArgumentContext,i) + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterApplyTransform" ): + listener.enterApplyTransform(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitApplyTransform" ): + listener.exitApplyTransform(self) + + + + def transform(self): + + localctx = SqlBaseParser.TransformContext(self, self._ctx, self.state) + self.enterRule(localctx, 232, self.RULE_transform) + self._la = 0 # Token type + try: + self.state = 2718 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,349,self._ctx) + if la_ == 1: + localctx = SqlBaseParser.IdentityTransformContext(self, localctx) + self.enterOuterAlt(localctx, 1) + self.state = 2705 + self.qualifiedName() + pass + + elif la_ == 2: + localctx = SqlBaseParser.ApplyTransformContext(self, localctx) + self.enterOuterAlt(localctx, 2) + self.state = 2706 + localctx.transformName = self.identifier() + self.state = 2707 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2708 + localctx._transformArgument = self.transformArgument() + localctx.argument.append(localctx._transformArgument) + self.state = 2713 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 2709 + self.match(SqlBaseParser.COMMA) + self.state = 2710 + localctx._transformArgument = self.transformArgument() + localctx.argument.append(localctx._transformArgument) + self.state = 2715 + self._errHandler.sync(self) + _la = self._input.LA(1) + + self.state = 2716 + self.match(SqlBaseParser.RIGHT_PAREN) + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class TransformArgumentContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def qualifiedName(self): + return self.getTypedRuleContext(SqlBaseParser.QualifiedNameContext,0) + + + def constant(self): + return self.getTypedRuleContext(SqlBaseParser.ConstantContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_transformArgument + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterTransformArgument" ): + listener.enterTransformArgument(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitTransformArgument" ): + listener.exitTransformArgument(self) + + + + + def transformArgument(self): + + localctx = SqlBaseParser.TransformArgumentContext(self, self._ctx, self.state) + self.enterRule(localctx, 234, self.RULE_transformArgument) + try: + self.state = 2722 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,350,self._ctx) + if la_ == 1: + self.enterOuterAlt(localctx, 1) + self.state = 2720 + self.qualifiedName() + pass + + elif la_ == 2: + self.enterOuterAlt(localctx, 2) + self.state = 2721 + self.constant() + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class ExpressionContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def booleanExpression(self): + return self.getTypedRuleContext(SqlBaseParser.BooleanExpressionContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_expression + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterExpression" ): + listener.enterExpression(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitExpression" ): + listener.exitExpression(self) + + + + + def expression(self): + + localctx = SqlBaseParser.ExpressionContext(self, self._ctx, self.state) + self.enterRule(localctx, 236, self.RULE_expression) + try: + self.enterOuterAlt(localctx, 1) + self.state = 2724 + self.booleanExpression(0) + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class ExpressionSeqContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def expression(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.ExpressionContext) + else: + return self.getTypedRuleContext(SqlBaseParser.ExpressionContext,i) + + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def getRuleIndex(self): + return SqlBaseParser.RULE_expressionSeq + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterExpressionSeq" ): + listener.enterExpressionSeq(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitExpressionSeq" ): + listener.exitExpressionSeq(self) + + + + + def expressionSeq(self): + + localctx = SqlBaseParser.ExpressionSeqContext(self, self._ctx, self.state) + self.enterRule(localctx, 238, self.RULE_expressionSeq) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 2726 + self.expression() + self.state = 2731 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 2727 + self.match(SqlBaseParser.COMMA) + self.state = 2728 + self.expression() + self.state = 2733 + self._errHandler.sync(self) + _la = self._input.LA(1) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class BooleanExpressionContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + + def getRuleIndex(self): + return SqlBaseParser.RULE_booleanExpression + + + def copyFrom(self, ctx:ParserRuleContext): + super().copyFrom(ctx) + + + class LogicalNotContext(BooleanExpressionContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.BooleanExpressionContext + super().__init__(parser) + self.copyFrom(ctx) + + def NOT(self): + return self.getToken(SqlBaseParser.NOT, 0) + def booleanExpression(self): + return self.getTypedRuleContext(SqlBaseParser.BooleanExpressionContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterLogicalNot" ): + listener.enterLogicalNot(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitLogicalNot" ): + listener.exitLogicalNot(self) + + + class PredicatedContext(BooleanExpressionContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.BooleanExpressionContext + super().__init__(parser) + self.copyFrom(ctx) + + def valueExpression(self): + return self.getTypedRuleContext(SqlBaseParser.ValueExpressionContext,0) + + def predicate(self): + return self.getTypedRuleContext(SqlBaseParser.PredicateContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterPredicated" ): + listener.enterPredicated(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitPredicated" ): + listener.exitPredicated(self) + + + class ExistsContext(BooleanExpressionContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.BooleanExpressionContext + super().__init__(parser) + self.copyFrom(ctx) + + def EXISTS(self): + return self.getToken(SqlBaseParser.EXISTS, 0) + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + def query(self): + return self.getTypedRuleContext(SqlBaseParser.QueryContext,0) + + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterExists" ): + listener.enterExists(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitExists" ): + listener.exitExists(self) + + + class LogicalBinaryContext(BooleanExpressionContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.BooleanExpressionContext + super().__init__(parser) + self.left = None # BooleanExpressionContext + self.operator = None # Token + self.right = None # BooleanExpressionContext + self.copyFrom(ctx) + + def booleanExpression(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.BooleanExpressionContext) + else: + return self.getTypedRuleContext(SqlBaseParser.BooleanExpressionContext,i) + + def AND(self): + return self.getToken(SqlBaseParser.AND, 0) + def OR(self): + return self.getToken(SqlBaseParser.OR, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterLogicalBinary" ): + listener.enterLogicalBinary(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitLogicalBinary" ): + listener.exitLogicalBinary(self) + + + + def booleanExpression(self, _p:int=0): + _parentctx = self._ctx + _parentState = self.state + localctx = SqlBaseParser.BooleanExpressionContext(self, self._ctx, _parentState) + _prevctx = localctx + _startState = 240 + self.enterRecursionRule(localctx, 240, self.RULE_booleanExpression, _p) + try: + self.enterOuterAlt(localctx, 1) + self.state = 2746 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,353,self._ctx) + if la_ == 1: + localctx = SqlBaseParser.LogicalNotContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + + self.state = 2735 + self.match(SqlBaseParser.NOT) + self.state = 2736 + self.booleanExpression(5) + pass + + elif la_ == 2: + localctx = SqlBaseParser.ExistsContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + self.state = 2737 + self.match(SqlBaseParser.EXISTS) + self.state = 2738 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2739 + self.query() + self.state = 2740 + self.match(SqlBaseParser.RIGHT_PAREN) + pass + + elif la_ == 3: + localctx = SqlBaseParser.PredicatedContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + self.state = 2742 + self.valueExpression(0) + self.state = 2744 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,352,self._ctx) + if la_ == 1: + self.state = 2743 + self.predicate() + + + pass + + + self._ctx.stop = self._input.LT(-1) + self.state = 2756 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,355,self._ctx) + while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt==1: + if self._parseListeners is not None: + self.triggerExitRuleEvent() + _prevctx = localctx + self.state = 2754 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,354,self._ctx) + if la_ == 1: + localctx = SqlBaseParser.LogicalBinaryContext(self, SqlBaseParser.BooleanExpressionContext(self, _parentctx, _parentState)) + localctx.left = _prevctx + self.pushNewRecursionContext(localctx, _startState, self.RULE_booleanExpression) + self.state = 2748 + if not self.precpred(self._ctx, 2): + from antlr4.error.Errors import FailedPredicateException + raise FailedPredicateException(self, "self.precpred(self._ctx, 2)") + self.state = 2749 + localctx.operator = self.match(SqlBaseParser.AND) + self.state = 2750 + localctx.right = self.booleanExpression(3) + pass + + elif la_ == 2: + localctx = SqlBaseParser.LogicalBinaryContext(self, SqlBaseParser.BooleanExpressionContext(self, _parentctx, _parentState)) + localctx.left = _prevctx + self.pushNewRecursionContext(localctx, _startState, self.RULE_booleanExpression) + self.state = 2751 + if not self.precpred(self._ctx, 1): + from antlr4.error.Errors import FailedPredicateException + raise FailedPredicateException(self, "self.precpred(self._ctx, 1)") + self.state = 2752 + localctx.operator = self.match(SqlBaseParser.OR) + self.state = 2753 + localctx.right = self.booleanExpression(2) + pass + + + self.state = 2758 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,355,self._ctx) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.unrollRecursionContexts(_parentctx) + return localctx + + + class PredicateContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self.kind = None # Token + self.lower = None # ValueExpressionContext + self.upper = None # ValueExpressionContext + self.pattern = None # ValueExpressionContext + self.quantifier = None # Token + self.escapeChar = None # StringLitContext + self.right = None # ValueExpressionContext + + def AND(self): + return self.getToken(SqlBaseParser.AND, 0) + + def BETWEEN(self): + return self.getToken(SqlBaseParser.BETWEEN, 0) + + def valueExpression(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.ValueExpressionContext) + else: + return self.getTypedRuleContext(SqlBaseParser.ValueExpressionContext,i) + + + def NOT(self): + return self.getToken(SqlBaseParser.NOT, 0) + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + + def expression(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.ExpressionContext) + else: + return self.getTypedRuleContext(SqlBaseParser.ExpressionContext,i) + + + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + + def IN(self): + return self.getToken(SqlBaseParser.IN, 0) + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def query(self): + return self.getTypedRuleContext(SqlBaseParser.QueryContext,0) + + + def RLIKE(self): + return self.getToken(SqlBaseParser.RLIKE, 0) + + def LIKE(self): + return self.getToken(SqlBaseParser.LIKE, 0) + + def ILIKE(self): + return self.getToken(SqlBaseParser.ILIKE, 0) + + def ANY(self): + return self.getToken(SqlBaseParser.ANY, 0) + + def SOME(self): + return self.getToken(SqlBaseParser.SOME, 0) + + def ALL(self): + return self.getToken(SqlBaseParser.ALL, 0) + + def ESCAPE(self): + return self.getToken(SqlBaseParser.ESCAPE, 0) + + def stringLit(self): + return self.getTypedRuleContext(SqlBaseParser.StringLitContext,0) + + + def IS(self): + return self.getToken(SqlBaseParser.IS, 0) + + def NULL(self): + return self.getToken(SqlBaseParser.NULL, 0) + + def TRUE(self): + return self.getToken(SqlBaseParser.TRUE, 0) + + def FALSE(self): + return self.getToken(SqlBaseParser.FALSE, 0) + + def UNKNOWN(self): + return self.getToken(SqlBaseParser.UNKNOWN, 0) + + def FROM(self): + return self.getToken(SqlBaseParser.FROM, 0) + + def DISTINCT(self): + return self.getToken(SqlBaseParser.DISTINCT, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_predicate + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterPredicate" ): + listener.enterPredicate(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitPredicate" ): + listener.exitPredicate(self) + + + + + def predicate(self): + + localctx = SqlBaseParser.PredicateContext(self, self._ctx, self.state) + self.enterRule(localctx, 242, self.RULE_predicate) + self._la = 0 # Token type + try: + self.state = 2841 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,369,self._ctx) + if la_ == 1: + self.enterOuterAlt(localctx, 1) + self.state = 2760 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==172: + self.state = 2759 + self.match(SqlBaseParser.NOT) + + + self.state = 2762 + localctx.kind = self.match(SqlBaseParser.BETWEEN) + self.state = 2763 + localctx.lower = self.valueExpression(0) + self.state = 2764 + self.match(SqlBaseParser.AND) + self.state = 2765 + localctx.upper = self.valueExpression(0) + pass + + elif la_ == 2: + self.enterOuterAlt(localctx, 2) + self.state = 2768 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==172: + self.state = 2767 + self.match(SqlBaseParser.NOT) + + + self.state = 2770 + localctx.kind = self.match(SqlBaseParser.IN) + self.state = 2771 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2772 + self.expression() + self.state = 2777 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 2773 + self.match(SqlBaseParser.COMMA) + self.state = 2774 + self.expression() + self.state = 2779 + self._errHandler.sync(self) + _la = self._input.LA(1) + + self.state = 2780 + self.match(SqlBaseParser.RIGHT_PAREN) + pass + + elif la_ == 3: + self.enterOuterAlt(localctx, 3) + self.state = 2783 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==172: + self.state = 2782 + self.match(SqlBaseParser.NOT) + + + self.state = 2785 + localctx.kind = self.match(SqlBaseParser.IN) + self.state = 2786 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2787 + self.query() + self.state = 2788 + self.match(SqlBaseParser.RIGHT_PAREN) + pass + + elif la_ == 4: + self.enterOuterAlt(localctx, 4) + self.state = 2791 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==172: + self.state = 2790 + self.match(SqlBaseParser.NOT) + + + self.state = 2793 + localctx.kind = self.match(SqlBaseParser.RLIKE) + self.state = 2794 + localctx.pattern = self.valueExpression(0) + pass + + elif la_ == 5: + self.enterOuterAlt(localctx, 5) + self.state = 2796 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==172: + self.state = 2795 + self.match(SqlBaseParser.NOT) + + + self.state = 2798 + localctx.kind = self._input.LT(1) + _la = self._input.LA(1) + if not(_la==142 or _la==143): + localctx.kind = self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 2799 + localctx.quantifier = self._input.LT(1) + _la = self._input.LA(1) + if not(_la==10 or _la==16 or _la==244): + localctx.quantifier = self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 2813 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,363,self._ctx) + if la_ == 1: + self.state = 2800 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2801 + self.match(SqlBaseParser.RIGHT_PAREN) + pass + + elif la_ == 2: + self.state = 2802 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2803 + self.expression() + self.state = 2808 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 2804 + self.match(SqlBaseParser.COMMA) + self.state = 2805 + self.expression() + self.state = 2810 + self._errHandler.sync(self) + _la = self._input.LA(1) + + self.state = 2811 + self.match(SqlBaseParser.RIGHT_PAREN) + pass + + + pass + + elif la_ == 6: + self.enterOuterAlt(localctx, 6) + self.state = 2816 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==172: + self.state = 2815 + self.match(SqlBaseParser.NOT) + + + self.state = 2818 + localctx.kind = self._input.LT(1) + _la = self._input.LA(1) + if not(_la==142 or _la==143): + localctx.kind = self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 2819 + localctx.pattern = self.valueExpression(0) + self.state = 2822 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,365,self._ctx) + if la_ == 1: + self.state = 2820 + self.match(SqlBaseParser.ESCAPE) + self.state = 2821 + localctx.escapeChar = self.stringLit() + + + pass + + elif la_ == 7: + self.enterOuterAlt(localctx, 7) + self.state = 2824 + self.match(SqlBaseParser.IS) + self.state = 2826 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==172: + self.state = 2825 + self.match(SqlBaseParser.NOT) + + + self.state = 2828 + localctx.kind = self.match(SqlBaseParser.NULL) + pass + + elif la_ == 8: + self.enterOuterAlt(localctx, 8) + self.state = 2829 + self.match(SqlBaseParser.IS) + self.state = 2831 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==172: + self.state = 2830 + self.match(SqlBaseParser.NOT) + + + self.state = 2833 + localctx.kind = self._input.LT(1) + _la = self._input.LA(1) + if not(_la==96 or _la==277 or _la==286): + localctx.kind = self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + pass + + elif la_ == 9: + self.enterOuterAlt(localctx, 9) + self.state = 2834 + self.match(SqlBaseParser.IS) + self.state = 2836 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==172: + self.state = 2835 + self.match(SqlBaseParser.NOT) + + + self.state = 2838 + localctx.kind = self.match(SqlBaseParser.DISTINCT) + self.state = 2839 + self.match(SqlBaseParser.FROM) + self.state = 2840 + localctx.right = self.valueExpression(0) + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class ValueExpressionContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + + def getRuleIndex(self): + return SqlBaseParser.RULE_valueExpression + + + def copyFrom(self, ctx:ParserRuleContext): + super().copyFrom(ctx) + + + class ValueExpressionDefaultContext(ValueExpressionContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.ValueExpressionContext + super().__init__(parser) + self.copyFrom(ctx) + + def primaryExpression(self): + return self.getTypedRuleContext(SqlBaseParser.PrimaryExpressionContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterValueExpressionDefault" ): + listener.enterValueExpressionDefault(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitValueExpressionDefault" ): + listener.exitValueExpressionDefault(self) + + + class ComparisonContext(ValueExpressionContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.ValueExpressionContext + super().__init__(parser) + self.left = None # ValueExpressionContext + self.right = None # ValueExpressionContext + self.copyFrom(ctx) + + def comparisonOperator(self): + return self.getTypedRuleContext(SqlBaseParser.ComparisonOperatorContext,0) + + def valueExpression(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.ValueExpressionContext) + else: + return self.getTypedRuleContext(SqlBaseParser.ValueExpressionContext,i) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterComparison" ): + listener.enterComparison(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitComparison" ): + listener.exitComparison(self) + + + class ArithmeticBinaryContext(ValueExpressionContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.ValueExpressionContext + super().__init__(parser) + self.left = None # ValueExpressionContext + self.operator = None # Token + self.right = None # ValueExpressionContext + self.copyFrom(ctx) + + def valueExpression(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.ValueExpressionContext) + else: + return self.getTypedRuleContext(SqlBaseParser.ValueExpressionContext,i) + + def ASTERISK(self): + return self.getToken(SqlBaseParser.ASTERISK, 0) + def SLASH(self): + return self.getToken(SqlBaseParser.SLASH, 0) + def PERCENT(self): + return self.getToken(SqlBaseParser.PERCENT, 0) + def DIV(self): + return self.getToken(SqlBaseParser.DIV, 0) + def PLUS(self): + return self.getToken(SqlBaseParser.PLUS, 0) + def MINUS(self): + return self.getToken(SqlBaseParser.MINUS, 0) + def CONCAT_PIPE(self): + return self.getToken(SqlBaseParser.CONCAT_PIPE, 0) + def AMPERSAND(self): + return self.getToken(SqlBaseParser.AMPERSAND, 0) + def HAT(self): + return self.getToken(SqlBaseParser.HAT, 0) + def PIPE(self): + return self.getToken(SqlBaseParser.PIPE, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterArithmeticBinary" ): + listener.enterArithmeticBinary(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitArithmeticBinary" ): + listener.exitArithmeticBinary(self) + + + class ArithmeticUnaryContext(ValueExpressionContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.ValueExpressionContext + super().__init__(parser) + self.operator = None # Token + self.copyFrom(ctx) + + def valueExpression(self): + return self.getTypedRuleContext(SqlBaseParser.ValueExpressionContext,0) + + def MINUS(self): + return self.getToken(SqlBaseParser.MINUS, 0) + def PLUS(self): + return self.getToken(SqlBaseParser.PLUS, 0) + def TILDE(self): + return self.getToken(SqlBaseParser.TILDE, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterArithmeticUnary" ): + listener.enterArithmeticUnary(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitArithmeticUnary" ): + listener.exitArithmeticUnary(self) + + + + def valueExpression(self, _p:int=0): + _parentctx = self._ctx + _parentState = self.state + localctx = SqlBaseParser.ValueExpressionContext(self, self._ctx, _parentState) + _prevctx = localctx + _startState = 244 + self.enterRecursionRule(localctx, 244, self.RULE_valueExpression, _p) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 2847 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,370,self._ctx) + if la_ == 1: + localctx = SqlBaseParser.ValueExpressionDefaultContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + + self.state = 2844 + self.primaryExpression(0) + pass + + elif la_ == 2: + localctx = SqlBaseParser.ArithmeticUnaryContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + self.state = 2845 + localctx.operator = self._input.LT(1) + _la = self._input.LA(1) + if not(((((_la - 316)) & ~0x3f) == 0 and ((1 << (_la - 316)) & 35) != 0)): + localctx.operator = self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 2846 + self.valueExpression(7) + pass + + + self._ctx.stop = self._input.LT(-1) + self.state = 2870 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,372,self._ctx) + while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt==1: + if self._parseListeners is not None: + self.triggerExitRuleEvent() + _prevctx = localctx + self.state = 2868 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,371,self._ctx) + if la_ == 1: + localctx = SqlBaseParser.ArithmeticBinaryContext(self, SqlBaseParser.ValueExpressionContext(self, _parentctx, _parentState)) + localctx.left = _prevctx + self.pushNewRecursionContext(localctx, _startState, self.RULE_valueExpression) + self.state = 2849 + if not self.precpred(self._ctx, 6): + from antlr4.error.Errors import FailedPredicateException + raise FailedPredicateException(self, "self.precpred(self._ctx, 6)") + self.state = 2850 + localctx.operator = self._input.LT(1) + _la = self._input.LA(1) + if not(_la==81 or ((((_la - 318)) & ~0x3f) == 0 and ((1 << (_la - 318)) & 7) != 0)): + localctx.operator = self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 2851 + localctx.right = self.valueExpression(7) + pass + + elif la_ == 2: + localctx = SqlBaseParser.ArithmeticBinaryContext(self, SqlBaseParser.ValueExpressionContext(self, _parentctx, _parentState)) + localctx.left = _prevctx + self.pushNewRecursionContext(localctx, _startState, self.RULE_valueExpression) + self.state = 2852 + if not self.precpred(self._ctx, 5): + from antlr4.error.Errors import FailedPredicateException + raise FailedPredicateException(self, "self.precpred(self._ctx, 5)") + self.state = 2853 + localctx.operator = self._input.LT(1) + _la = self._input.LA(1) + if not(((((_la - 316)) & ~0x3f) == 0 and ((1 << (_la - 316)) & 259) != 0)): + localctx.operator = self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 2854 + localctx.right = self.valueExpression(6) + pass + + elif la_ == 3: + localctx = SqlBaseParser.ArithmeticBinaryContext(self, SqlBaseParser.ValueExpressionContext(self, _parentctx, _parentState)) + localctx.left = _prevctx + self.pushNewRecursionContext(localctx, _startState, self.RULE_valueExpression) + self.state = 2855 + if not self.precpred(self._ctx, 4): + from antlr4.error.Errors import FailedPredicateException + raise FailedPredicateException(self, "self.precpred(self._ctx, 4)") + self.state = 2856 + localctx.operator = self.match(SqlBaseParser.AMPERSAND) + self.state = 2857 + localctx.right = self.valueExpression(5) + pass + + elif la_ == 4: + localctx = SqlBaseParser.ArithmeticBinaryContext(self, SqlBaseParser.ValueExpressionContext(self, _parentctx, _parentState)) + localctx.left = _prevctx + self.pushNewRecursionContext(localctx, _startState, self.RULE_valueExpression) + self.state = 2858 + if not self.precpred(self._ctx, 3): + from antlr4.error.Errors import FailedPredicateException + raise FailedPredicateException(self, "self.precpred(self._ctx, 3)") + self.state = 2859 + localctx.operator = self.match(SqlBaseParser.HAT) + self.state = 2860 + localctx.right = self.valueExpression(4) + pass + + elif la_ == 5: + localctx = SqlBaseParser.ArithmeticBinaryContext(self, SqlBaseParser.ValueExpressionContext(self, _parentctx, _parentState)) + localctx.left = _prevctx + self.pushNewRecursionContext(localctx, _startState, self.RULE_valueExpression) + self.state = 2861 + if not self.precpred(self._ctx, 2): + from antlr4.error.Errors import FailedPredicateException + raise FailedPredicateException(self, "self.precpred(self._ctx, 2)") + self.state = 2862 + localctx.operator = self.match(SqlBaseParser.PIPE) + self.state = 2863 + localctx.right = self.valueExpression(3) + pass + + elif la_ == 6: + localctx = SqlBaseParser.ComparisonContext(self, SqlBaseParser.ValueExpressionContext(self, _parentctx, _parentState)) + localctx.left = _prevctx + self.pushNewRecursionContext(localctx, _startState, self.RULE_valueExpression) + self.state = 2864 + if not self.precpred(self._ctx, 1): + from antlr4.error.Errors import FailedPredicateException + raise FailedPredicateException(self, "self.precpred(self._ctx, 1)") + self.state = 2865 + self.comparisonOperator() + self.state = 2866 + localctx.right = self.valueExpression(2) + pass + + + self.state = 2872 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,372,self._ctx) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.unrollRecursionContexts(_parentctx) + return localctx + + + class DatetimeUnitContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def YEAR(self): + return self.getToken(SqlBaseParser.YEAR, 0) + + def QUARTER(self): + return self.getToken(SqlBaseParser.QUARTER, 0) + + def MONTH(self): + return self.getToken(SqlBaseParser.MONTH, 0) + + def WEEK(self): + return self.getToken(SqlBaseParser.WEEK, 0) + + def DAY(self): + return self.getToken(SqlBaseParser.DAY, 0) + + def DAYOFYEAR(self): + return self.getToken(SqlBaseParser.DAYOFYEAR, 0) + + def HOUR(self): + return self.getToken(SqlBaseParser.HOUR, 0) + + def MINUTE(self): + return self.getToken(SqlBaseParser.MINUTE, 0) + + def SECOND(self): + return self.getToken(SqlBaseParser.SECOND, 0) + + def MILLISECOND(self): + return self.getToken(SqlBaseParser.MILLISECOND, 0) + + def MICROSECOND(self): + return self.getToken(SqlBaseParser.MICROSECOND, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_datetimeUnit + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterDatetimeUnit" ): + listener.enterDatetimeUnit(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitDatetimeUnit" ): + listener.exitDatetimeUnit(self) + + + + + def datetimeUnit(self): + + localctx = SqlBaseParser.DatetimeUnitContext(self, self._ctx, self.state) + self.enterRule(localctx, 246, self.RULE_datetimeUnit) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 2873 + _la = self._input.LA(1) + if not(_la==61 or _la==63 or ((((_la - 117)) & ~0x3f) == 0 and ((1 << (_la - 117)) & 93458488360961) != 0) or _la==204 or _la==229 or _la==298 or _la==305): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class PrimaryExpressionContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + + def getRuleIndex(self): + return SqlBaseParser.RULE_primaryExpression + + + def copyFrom(self, ctx:ParserRuleContext): + super().copyFrom(ctx) + + + class StructContext(PrimaryExpressionContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.PrimaryExpressionContext + super().__init__(parser) + self._namedExpression = None # NamedExpressionContext + self.argument = list() # of NamedExpressionContexts + self.copyFrom(ctx) + + def STRUCT(self): + return self.getToken(SqlBaseParser.STRUCT, 0) + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + def namedExpression(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.NamedExpressionContext) + else: + return self.getTypedRuleContext(SqlBaseParser.NamedExpressionContext,i) + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterStruct" ): + listener.enterStruct(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitStruct" ): + listener.exitStruct(self) + + + class DereferenceContext(PrimaryExpressionContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.PrimaryExpressionContext + super().__init__(parser) + self.base = None # PrimaryExpressionContext + self.fieldName = None # IdentifierContext + self.copyFrom(ctx) + + def DOT(self): + return self.getToken(SqlBaseParser.DOT, 0) + def primaryExpression(self): + return self.getTypedRuleContext(SqlBaseParser.PrimaryExpressionContext,0) + + def identifier(self): + return self.getTypedRuleContext(SqlBaseParser.IdentifierContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterDereference" ): + listener.enterDereference(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitDereference" ): + listener.exitDereference(self) + + + class TimestampaddContext(PrimaryExpressionContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.PrimaryExpressionContext + super().__init__(parser) + self.name = None # Token + self.unit = None # DatetimeUnitContext + self.unitsAmount = None # ValueExpressionContext + self.timestamp = None # ValueExpressionContext + self.copyFrom(ctx) + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + def datetimeUnit(self): + return self.getTypedRuleContext(SqlBaseParser.DatetimeUnitContext,0) + + def valueExpression(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.ValueExpressionContext) + else: + return self.getTypedRuleContext(SqlBaseParser.ValueExpressionContext,i) + + def TIMESTAMPADD(self): + return self.getToken(SqlBaseParser.TIMESTAMPADD, 0) + def DATEADD(self): + return self.getToken(SqlBaseParser.DATEADD, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterTimestampadd" ): + listener.enterTimestampadd(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitTimestampadd" ): + listener.exitTimestampadd(self) + + + class SubstringContext(PrimaryExpressionContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.PrimaryExpressionContext + super().__init__(parser) + self.str_ = None # ValueExpressionContext + self.pos = None # ValueExpressionContext + self.len_ = None # ValueExpressionContext + self.copyFrom(ctx) + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + def SUBSTR(self): + return self.getToken(SqlBaseParser.SUBSTR, 0) + def SUBSTRING(self): + return self.getToken(SqlBaseParser.SUBSTRING, 0) + def valueExpression(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.ValueExpressionContext) + else: + return self.getTypedRuleContext(SqlBaseParser.ValueExpressionContext,i) + + def FROM(self): + return self.getToken(SqlBaseParser.FROM, 0) + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + def FOR(self): + return self.getToken(SqlBaseParser.FOR, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSubstring" ): + listener.enterSubstring(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSubstring" ): + listener.exitSubstring(self) + + + class CastContext(PrimaryExpressionContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.PrimaryExpressionContext + super().__init__(parser) + self.name = None # Token + self.copyFrom(ctx) + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + def expression(self): + return self.getTypedRuleContext(SqlBaseParser.ExpressionContext,0) + + def AS(self): + return self.getToken(SqlBaseParser.AS, 0) + def dataType(self): + return self.getTypedRuleContext(SqlBaseParser.DataTypeContext,0) + + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + def CAST(self): + return self.getToken(SqlBaseParser.CAST, 0) + def TRY_CAST(self): + return self.getToken(SqlBaseParser.TRY_CAST, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterCast" ): + listener.enterCast(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitCast" ): + listener.exitCast(self) + + + class LambdaContext(PrimaryExpressionContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.PrimaryExpressionContext + super().__init__(parser) + self.copyFrom(ctx) + + def identifier(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.IdentifierContext) + else: + return self.getTypedRuleContext(SqlBaseParser.IdentifierContext,i) + + def ARROW(self): + return self.getToken(SqlBaseParser.ARROW, 0) + def expression(self): + return self.getTypedRuleContext(SqlBaseParser.ExpressionContext,0) + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterLambda" ): + listener.enterLambda(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitLambda" ): + listener.exitLambda(self) + + + class ParenthesizedExpressionContext(PrimaryExpressionContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.PrimaryExpressionContext + super().__init__(parser) + self.copyFrom(ctx) + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + def expression(self): + return self.getTypedRuleContext(SqlBaseParser.ExpressionContext,0) + + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterParenthesizedExpression" ): + listener.enterParenthesizedExpression(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitParenthesizedExpression" ): + listener.exitParenthesizedExpression(self) + + + class Any_valueContext(PrimaryExpressionContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.PrimaryExpressionContext + super().__init__(parser) + self.copyFrom(ctx) + + def ANY_VALUE(self): + return self.getToken(SqlBaseParser.ANY_VALUE, 0) + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + def expression(self): + return self.getTypedRuleContext(SqlBaseParser.ExpressionContext,0) + + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + def IGNORE(self): + return self.getToken(SqlBaseParser.IGNORE, 0) + def NULLS(self): + return self.getToken(SqlBaseParser.NULLS, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterAny_value" ): + listener.enterAny_value(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitAny_value" ): + listener.exitAny_value(self) + + + class TrimContext(PrimaryExpressionContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.PrimaryExpressionContext + super().__init__(parser) + self.trimOption = None # Token + self.trimStr = None # ValueExpressionContext + self.srcStr = None # ValueExpressionContext + self.copyFrom(ctx) + + def TRIM(self): + return self.getToken(SqlBaseParser.TRIM, 0) + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + def FROM(self): + return self.getToken(SqlBaseParser.FROM, 0) + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + def valueExpression(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.ValueExpressionContext) + else: + return self.getTypedRuleContext(SqlBaseParser.ValueExpressionContext,i) + + def BOTH(self): + return self.getToken(SqlBaseParser.BOTH, 0) + def LEADING(self): + return self.getToken(SqlBaseParser.LEADING, 0) + def TRAILING(self): + return self.getToken(SqlBaseParser.TRAILING, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterTrim" ): + listener.enterTrim(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitTrim" ): + listener.exitTrim(self) + + + class SimpleCaseContext(PrimaryExpressionContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.PrimaryExpressionContext + super().__init__(parser) + self.value = None # ExpressionContext + self.elseExpression = None # ExpressionContext + self.copyFrom(ctx) + + def CASE(self): + return self.getToken(SqlBaseParser.CASE, 0) + def END(self): + return self.getToken(SqlBaseParser.END, 0) + def expression(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.ExpressionContext) + else: + return self.getTypedRuleContext(SqlBaseParser.ExpressionContext,i) + + def whenClause(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.WhenClauseContext) + else: + return self.getTypedRuleContext(SqlBaseParser.WhenClauseContext,i) + + def ELSE(self): + return self.getToken(SqlBaseParser.ELSE, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSimpleCase" ): + listener.enterSimpleCase(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSimpleCase" ): + listener.exitSimpleCase(self) + + + class CurrentLikeContext(PrimaryExpressionContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.PrimaryExpressionContext + super().__init__(parser) + self.name = None # Token + self.copyFrom(ctx) + + def CURRENT_DATE(self): + return self.getToken(SqlBaseParser.CURRENT_DATE, 0) + def CURRENT_TIMESTAMP(self): + return self.getToken(SqlBaseParser.CURRENT_TIMESTAMP, 0) + def CURRENT_USER(self): + return self.getToken(SqlBaseParser.CURRENT_USER, 0) + def USER(self): + return self.getToken(SqlBaseParser.USER, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterCurrentLike" ): + listener.enterCurrentLike(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitCurrentLike" ): + listener.exitCurrentLike(self) + + + class ColumnReferenceContext(PrimaryExpressionContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.PrimaryExpressionContext + super().__init__(parser) + self.copyFrom(ctx) + + def identifier(self): + return self.getTypedRuleContext(SqlBaseParser.IdentifierContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterColumnReference" ): + listener.enterColumnReference(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitColumnReference" ): + listener.exitColumnReference(self) + + + class RowConstructorContext(PrimaryExpressionContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.PrimaryExpressionContext + super().__init__(parser) + self.copyFrom(ctx) + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + def namedExpression(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.NamedExpressionContext) + else: + return self.getTypedRuleContext(SqlBaseParser.NamedExpressionContext,i) + + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterRowConstructor" ): + listener.enterRowConstructor(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitRowConstructor" ): + listener.exitRowConstructor(self) + + + class LastContext(PrimaryExpressionContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.PrimaryExpressionContext + super().__init__(parser) + self.copyFrom(ctx) + + def LAST(self): + return self.getToken(SqlBaseParser.LAST, 0) + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + def expression(self): + return self.getTypedRuleContext(SqlBaseParser.ExpressionContext,0) + + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + def IGNORE(self): + return self.getToken(SqlBaseParser.IGNORE, 0) + def NULLS(self): + return self.getToken(SqlBaseParser.NULLS, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterLast" ): + listener.enterLast(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitLast" ): + listener.exitLast(self) + + + class StarContext(PrimaryExpressionContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.PrimaryExpressionContext + super().__init__(parser) + self.copyFrom(ctx) + + def ASTERISK(self): + return self.getToken(SqlBaseParser.ASTERISK, 0) + def qualifiedName(self): + return self.getTypedRuleContext(SqlBaseParser.QualifiedNameContext,0) + + def DOT(self): + return self.getToken(SqlBaseParser.DOT, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterStar" ): + listener.enterStar(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitStar" ): + listener.exitStar(self) + + + class OverlayContext(PrimaryExpressionContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.PrimaryExpressionContext + super().__init__(parser) + self.input_ = None # ValueExpressionContext + self.replace = None # ValueExpressionContext + self.position = None # ValueExpressionContext + self.length = None # ValueExpressionContext + self.copyFrom(ctx) + + def OVERLAY(self): + return self.getToken(SqlBaseParser.OVERLAY, 0) + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + def PLACING(self): + return self.getToken(SqlBaseParser.PLACING, 0) + def FROM(self): + return self.getToken(SqlBaseParser.FROM, 0) + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + def valueExpression(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.ValueExpressionContext) + else: + return self.getTypedRuleContext(SqlBaseParser.ValueExpressionContext,i) + + def FOR(self): + return self.getToken(SqlBaseParser.FOR, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterOverlay" ): + listener.enterOverlay(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitOverlay" ): + listener.exitOverlay(self) + + + class SubscriptContext(PrimaryExpressionContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.PrimaryExpressionContext + super().__init__(parser) + self.value = None # PrimaryExpressionContext + self.index = None # ValueExpressionContext + self.copyFrom(ctx) + + def LEFT_BRACKET(self): + return self.getToken(SqlBaseParser.LEFT_BRACKET, 0) + def RIGHT_BRACKET(self): + return self.getToken(SqlBaseParser.RIGHT_BRACKET, 0) + def primaryExpression(self): + return self.getTypedRuleContext(SqlBaseParser.PrimaryExpressionContext,0) + + def valueExpression(self): + return self.getTypedRuleContext(SqlBaseParser.ValueExpressionContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSubscript" ): + listener.enterSubscript(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSubscript" ): + listener.exitSubscript(self) + + + class TimestampdiffContext(PrimaryExpressionContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.PrimaryExpressionContext + super().__init__(parser) + self.name = None # Token + self.unit = None # DatetimeUnitContext + self.startTimestamp = None # ValueExpressionContext + self.endTimestamp = None # ValueExpressionContext + self.copyFrom(ctx) + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + def datetimeUnit(self): + return self.getTypedRuleContext(SqlBaseParser.DatetimeUnitContext,0) + + def valueExpression(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.ValueExpressionContext) + else: + return self.getTypedRuleContext(SqlBaseParser.ValueExpressionContext,i) + + def TIMESTAMPDIFF(self): + return self.getToken(SqlBaseParser.TIMESTAMPDIFF, 0) + def DATEDIFF(self): + return self.getToken(SqlBaseParser.DATEDIFF, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterTimestampdiff" ): + listener.enterTimestampdiff(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitTimestampdiff" ): + listener.exitTimestampdiff(self) + + + class SubqueryExpressionContext(PrimaryExpressionContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.PrimaryExpressionContext + super().__init__(parser) + self.copyFrom(ctx) + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + def query(self): + return self.getTypedRuleContext(SqlBaseParser.QueryContext,0) + + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSubqueryExpression" ): + listener.enterSubqueryExpression(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSubqueryExpression" ): + listener.exitSubqueryExpression(self) + + + class ConstantDefaultContext(PrimaryExpressionContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.PrimaryExpressionContext + super().__init__(parser) + self.copyFrom(ctx) + + def constant(self): + return self.getTypedRuleContext(SqlBaseParser.ConstantContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterConstantDefault" ): + listener.enterConstantDefault(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitConstantDefault" ): + listener.exitConstantDefault(self) + + + class ExtractContext(PrimaryExpressionContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.PrimaryExpressionContext + super().__init__(parser) + self.field = None # IdentifierContext + self.source = None # ValueExpressionContext + self.copyFrom(ctx) + + def EXTRACT(self): + return self.getToken(SqlBaseParser.EXTRACT, 0) + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + def FROM(self): + return self.getToken(SqlBaseParser.FROM, 0) + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + def identifier(self): + return self.getTypedRuleContext(SqlBaseParser.IdentifierContext,0) + + def valueExpression(self): + return self.getTypedRuleContext(SqlBaseParser.ValueExpressionContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterExtract" ): + listener.enterExtract(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitExtract" ): + listener.exitExtract(self) + + + class PercentileContext(PrimaryExpressionContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.PrimaryExpressionContext + super().__init__(parser) + self.name = None # Token + self.percentage = None # ValueExpressionContext + self.where = None # BooleanExpressionContext + self.copyFrom(ctx) + + def LEFT_PAREN(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.LEFT_PAREN) + else: + return self.getToken(SqlBaseParser.LEFT_PAREN, i) + def RIGHT_PAREN(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.RIGHT_PAREN) + else: + return self.getToken(SqlBaseParser.RIGHT_PAREN, i) + def WITHIN(self): + return self.getToken(SqlBaseParser.WITHIN, 0) + def GROUP(self): + return self.getToken(SqlBaseParser.GROUP, 0) + def ORDER(self): + return self.getToken(SqlBaseParser.ORDER, 0) + def BY(self): + return self.getToken(SqlBaseParser.BY, 0) + def sortItem(self): + return self.getTypedRuleContext(SqlBaseParser.SortItemContext,0) + + def valueExpression(self): + return self.getTypedRuleContext(SqlBaseParser.ValueExpressionContext,0) + + def PERCENTILE_CONT(self): + return self.getToken(SqlBaseParser.PERCENTILE_CONT, 0) + def PERCENTILE_DISC(self): + return self.getToken(SqlBaseParser.PERCENTILE_DISC, 0) + def FILTER(self): + return self.getToken(SqlBaseParser.FILTER, 0) + def WHERE(self): + return self.getToken(SqlBaseParser.WHERE, 0) + def OVER(self): + return self.getToken(SqlBaseParser.OVER, 0) + def windowSpec(self): + return self.getTypedRuleContext(SqlBaseParser.WindowSpecContext,0) + + def booleanExpression(self): + return self.getTypedRuleContext(SqlBaseParser.BooleanExpressionContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterPercentile" ): + listener.enterPercentile(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitPercentile" ): + listener.exitPercentile(self) + + + class FunctionCallContext(PrimaryExpressionContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.PrimaryExpressionContext + super().__init__(parser) + self._expression = None # ExpressionContext + self.argument = list() # of ExpressionContexts + self.where = None # BooleanExpressionContext + self.nullsOption = None # Token + self.copyFrom(ctx) + + def functionName(self): + return self.getTypedRuleContext(SqlBaseParser.FunctionNameContext,0) + + def LEFT_PAREN(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.LEFT_PAREN) + else: + return self.getToken(SqlBaseParser.LEFT_PAREN, i) + def RIGHT_PAREN(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.RIGHT_PAREN) + else: + return self.getToken(SqlBaseParser.RIGHT_PAREN, i) + def FILTER(self): + return self.getToken(SqlBaseParser.FILTER, 0) + def WHERE(self): + return self.getToken(SqlBaseParser.WHERE, 0) + def NULLS(self): + return self.getToken(SqlBaseParser.NULLS, 0) + def OVER(self): + return self.getToken(SqlBaseParser.OVER, 0) + def windowSpec(self): + return self.getTypedRuleContext(SqlBaseParser.WindowSpecContext,0) + + def expression(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.ExpressionContext) + else: + return self.getTypedRuleContext(SqlBaseParser.ExpressionContext,i) + + def booleanExpression(self): + return self.getTypedRuleContext(SqlBaseParser.BooleanExpressionContext,0) + + def IGNORE(self): + return self.getToken(SqlBaseParser.IGNORE, 0) + def RESPECT(self): + return self.getToken(SqlBaseParser.RESPECT, 0) + def setQuantifier(self): + return self.getTypedRuleContext(SqlBaseParser.SetQuantifierContext,0) + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterFunctionCall" ): + listener.enterFunctionCall(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitFunctionCall" ): + listener.exitFunctionCall(self) + + + class SearchedCaseContext(PrimaryExpressionContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.PrimaryExpressionContext + super().__init__(parser) + self.elseExpression = None # ExpressionContext + self.copyFrom(ctx) + + def CASE(self): + return self.getToken(SqlBaseParser.CASE, 0) + def END(self): + return self.getToken(SqlBaseParser.END, 0) + def whenClause(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.WhenClauseContext) + else: + return self.getTypedRuleContext(SqlBaseParser.WhenClauseContext,i) + + def ELSE(self): + return self.getToken(SqlBaseParser.ELSE, 0) + def expression(self): + return self.getTypedRuleContext(SqlBaseParser.ExpressionContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSearchedCase" ): + listener.enterSearchedCase(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSearchedCase" ): + listener.exitSearchedCase(self) + + + class PositionContext(PrimaryExpressionContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.PrimaryExpressionContext + super().__init__(parser) + self.substr = None # ValueExpressionContext + self.str_ = None # ValueExpressionContext + self.copyFrom(ctx) + + def POSITION(self): + return self.getToken(SqlBaseParser.POSITION, 0) + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + def IN(self): + return self.getToken(SqlBaseParser.IN, 0) + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + def valueExpression(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.ValueExpressionContext) + else: + return self.getTypedRuleContext(SqlBaseParser.ValueExpressionContext,i) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterPosition" ): + listener.enterPosition(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitPosition" ): + listener.exitPosition(self) + + + class FirstContext(PrimaryExpressionContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.PrimaryExpressionContext + super().__init__(parser) + self.copyFrom(ctx) + + def FIRST(self): + return self.getToken(SqlBaseParser.FIRST, 0) + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + def expression(self): + return self.getTypedRuleContext(SqlBaseParser.ExpressionContext,0) + + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + def IGNORE(self): + return self.getToken(SqlBaseParser.IGNORE, 0) + def NULLS(self): + return self.getToken(SqlBaseParser.NULLS, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterFirst" ): + listener.enterFirst(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitFirst" ): + listener.exitFirst(self) + + + + def primaryExpression(self, _p:int=0): + _parentctx = self._ctx + _parentState = self.state + localctx = SqlBaseParser.PrimaryExpressionContext(self, self._ctx, _parentState) + _prevctx = localctx + _startState = 248 + self.enterRecursionRule(localctx, 248, self.RULE_primaryExpression, _p) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 3113 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,396,self._ctx) + if la_ == 1: + localctx = SqlBaseParser.CurrentLikeContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + + self.state = 2876 + localctx.name = self._input.LT(1) + _la = self._input.LA(1) + if not((((_la) & ~0x3f) == 0 and ((1 << _la) & 1873497444986126336) != 0) or _la==292): + localctx.name = self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + pass + + elif la_ == 2: + localctx = SqlBaseParser.TimestampaddContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + self.state = 2877 + localctx.name = self._input.LT(1) + _la = self._input.LA(1) + if not(_la==67 or _la==268): + localctx.name = self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 2878 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2879 + localctx.unit = self.datetimeUnit() + self.state = 2880 + self.match(SqlBaseParser.COMMA) + self.state = 2881 + localctx.unitsAmount = self.valueExpression(0) + self.state = 2882 + self.match(SqlBaseParser.COMMA) + self.state = 2883 + localctx.timestamp = self.valueExpression(0) + self.state = 2884 + self.match(SqlBaseParser.RIGHT_PAREN) + pass + + elif la_ == 3: + localctx = SqlBaseParser.TimestampdiffContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + self.state = 2886 + localctx.name = self._input.LT(1) + _la = self._input.LA(1) + if not(_la==68 or _la==269): + localctx.name = self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 2887 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2888 + localctx.unit = self.datetimeUnit() + self.state = 2889 + self.match(SqlBaseParser.COMMA) + self.state = 2890 + localctx.startTimestamp = self.valueExpression(0) + self.state = 2891 + self.match(SqlBaseParser.COMMA) + self.state = 2892 + localctx.endTimestamp = self.valueExpression(0) + self.state = 2893 + self.match(SqlBaseParser.RIGHT_PAREN) + pass + + elif la_ == 4: + localctx = SqlBaseParser.SearchedCaseContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + self.state = 2895 + self.match(SqlBaseParser.CASE) + self.state = 2897 + self._errHandler.sync(self) + _la = self._input.LA(1) + while True: + self.state = 2896 + self.whenClause() + self.state = 2899 + self._errHandler.sync(self) + _la = self._input.LA(1) + if not (_la==300): + break + + self.state = 2903 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==83: + self.state = 2901 + self.match(SqlBaseParser.ELSE) + self.state = 2902 + localctx.elseExpression = self.expression() + + + self.state = 2905 + self.match(SqlBaseParser.END) + pass + + elif la_ == 5: + localctx = SqlBaseParser.SimpleCaseContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + self.state = 2907 + self.match(SqlBaseParser.CASE) + self.state = 2908 + localctx.value = self.expression() + self.state = 2910 + self._errHandler.sync(self) + _la = self._input.LA(1) + while True: + self.state = 2909 + self.whenClause() + self.state = 2912 + self._errHandler.sync(self) + _la = self._input.LA(1) + if not (_la==300): + break + + self.state = 2916 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==83: + self.state = 2914 + self.match(SqlBaseParser.ELSE) + self.state = 2915 + localctx.elseExpression = self.expression() + + + self.state = 2918 + self.match(SqlBaseParser.END) + pass + + elif la_ == 6: + localctx = SqlBaseParser.CastContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + self.state = 2920 + localctx.name = self._input.LT(1) + _la = self._input.LA(1) + if not(_la==32 or _la==279): + localctx.name = self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 2921 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2922 + self.expression() + self.state = 2923 + self.match(SqlBaseParser.AS) + self.state = 2924 + self.dataType() + self.state = 2925 + self.match(SqlBaseParser.RIGHT_PAREN) + pass + + elif la_ == 7: + localctx = SqlBaseParser.StructContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + self.state = 2927 + self.match(SqlBaseParser.STRUCT) + self.state = 2928 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2937 + self._errHandler.sync(self) + _la = self._input.LA(1) + if (((_la) & ~0x3f) == 0 and ((1 << _la) & -252) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & -1) != 0) or ((((_la - 128)) & ~0x3f) == 0 and ((1 << (_la - 128)) & -1) != 0) or ((((_la - 192)) & ~0x3f) == 0 and ((1 << (_la - 192)) & -1) != 0) or ((((_la - 256)) & ~0x3f) == 0 and ((1 << (_la - 256)) & 8074954131875299327) != 0) or ((((_la - 321)) & ~0x3f) == 0 and ((1 << (_la - 321)) & 4193825) != 0): + self.state = 2929 + localctx._namedExpression = self.namedExpression() + localctx.argument.append(localctx._namedExpression) + self.state = 2934 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 2930 + self.match(SqlBaseParser.COMMA) + self.state = 2931 + localctx._namedExpression = self.namedExpression() + localctx.argument.append(localctx._namedExpression) + self.state = 2936 + self._errHandler.sync(self) + _la = self._input.LA(1) + + + + self.state = 2939 + self.match(SqlBaseParser.RIGHT_PAREN) + pass + + elif la_ == 8: + localctx = SqlBaseParser.FirstContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + self.state = 2940 + self.match(SqlBaseParser.FIRST) + self.state = 2941 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2942 + self.expression() + self.state = 2945 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==120: + self.state = 2943 + self.match(SqlBaseParser.IGNORE) + self.state = 2944 + self.match(SqlBaseParser.NULLS) + + + self.state = 2947 + self.match(SqlBaseParser.RIGHT_PAREN) + pass + + elif la_ == 9: + localctx = SqlBaseParser.Any_valueContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + self.state = 2949 + self.match(SqlBaseParser.ANY_VALUE) + self.state = 2950 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2951 + self.expression() + self.state = 2954 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==120: + self.state = 2952 + self.match(SqlBaseParser.IGNORE) + self.state = 2953 + self.match(SqlBaseParser.NULLS) + + + self.state = 2956 + self.match(SqlBaseParser.RIGHT_PAREN) + pass + + elif la_ == 10: + localctx = SqlBaseParser.LastContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + self.state = 2958 + self.match(SqlBaseParser.LAST) + self.state = 2959 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2960 + self.expression() + self.state = 2963 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==120: + self.state = 2961 + self.match(SqlBaseParser.IGNORE) + self.state = 2962 + self.match(SqlBaseParser.NULLS) + + + self.state = 2965 + self.match(SqlBaseParser.RIGHT_PAREN) + pass + + elif la_ == 11: + localctx = SqlBaseParser.PositionContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + self.state = 2967 + self.match(SqlBaseParser.POSITION) + self.state = 2968 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2969 + localctx.substr = self.valueExpression(0) + self.state = 2970 + self.match(SqlBaseParser.IN) + self.state = 2971 + localctx.str_ = self.valueExpression(0) + self.state = 2972 + self.match(SqlBaseParser.RIGHT_PAREN) + pass + + elif la_ == 12: + localctx = SqlBaseParser.ConstantDefaultContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + self.state = 2974 + self.constant() + pass + + elif la_ == 13: + localctx = SqlBaseParser.StarContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + self.state = 2975 + self.match(SqlBaseParser.ASTERISK) + pass + + elif la_ == 14: + localctx = SqlBaseParser.StarContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + self.state = 2976 + self.qualifiedName() + self.state = 2977 + self.match(SqlBaseParser.DOT) + self.state = 2978 + self.match(SqlBaseParser.ASTERISK) + pass + + elif la_ == 15: + localctx = SqlBaseParser.RowConstructorContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + self.state = 2980 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2981 + self.namedExpression() + self.state = 2984 + self._errHandler.sync(self) + _la = self._input.LA(1) + while True: + self.state = 2982 + self.match(SqlBaseParser.COMMA) + self.state = 2983 + self.namedExpression() + self.state = 2986 + self._errHandler.sync(self) + _la = self._input.LA(1) + if not (_la==4): + break + + self.state = 2988 + self.match(SqlBaseParser.RIGHT_PAREN) + pass + + elif la_ == 16: + localctx = SqlBaseParser.SubqueryExpressionContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + self.state = 2990 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 2991 + self.query() + self.state = 2992 + self.match(SqlBaseParser.RIGHT_PAREN) + pass + + elif la_ == 17: + localctx = SqlBaseParser.FunctionCallContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + self.state = 2994 + self.functionName() + self.state = 2995 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 3007 + self._errHandler.sync(self) + _la = self._input.LA(1) + if (((_la) & ~0x3f) == 0 and ((1 << _la) & -252) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & -1) != 0) or ((((_la - 128)) & ~0x3f) == 0 and ((1 << (_la - 128)) & -1) != 0) or ((((_la - 192)) & ~0x3f) == 0 and ((1 << (_la - 192)) & -1) != 0) or ((((_la - 256)) & ~0x3f) == 0 and ((1 << (_la - 256)) & 8074954131875299327) != 0) or ((((_la - 321)) & ~0x3f) == 0 and ((1 << (_la - 321)) & 4193825) != 0): + self.state = 2997 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,383,self._ctx) + if la_ == 1: + self.state = 2996 + self.setQuantifier() + + + self.state = 2999 + localctx._expression = self.expression() + localctx.argument.append(localctx._expression) + self.state = 3004 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 3000 + self.match(SqlBaseParser.COMMA) + self.state = 3001 + localctx._expression = self.expression() + localctx.argument.append(localctx._expression) + self.state = 3006 + self._errHandler.sync(self) + _la = self._input.LA(1) + + + + self.state = 3009 + self.match(SqlBaseParser.RIGHT_PAREN) + self.state = 3016 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,386,self._ctx) + if la_ == 1: + self.state = 3010 + self.match(SqlBaseParser.FILTER) + self.state = 3011 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 3012 + self.match(SqlBaseParser.WHERE) + self.state = 3013 + localctx.where = self.booleanExpression(0) + self.state = 3014 + self.match(SqlBaseParser.RIGHT_PAREN) + + + self.state = 3020 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,387,self._ctx) + if la_ == 1: + self.state = 3018 + localctx.nullsOption = self._input.LT(1) + _la = self._input.LA(1) + if not(_la==120 or _la==218): + localctx.nullsOption = self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 3019 + self.match(SqlBaseParser.NULLS) + + + self.state = 3024 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,388,self._ctx) + if la_ == 1: + self.state = 3022 + self.match(SqlBaseParser.OVER) + self.state = 3023 + self.windowSpec() + + + pass + + elif la_ == 18: + localctx = SqlBaseParser.LambdaContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + self.state = 3026 + self.identifier() + self.state = 3027 + self.match(SqlBaseParser.ARROW) + self.state = 3028 + self.expression() + pass + + elif la_ == 19: + localctx = SqlBaseParser.LambdaContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + self.state = 3030 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 3031 + self.identifier() + self.state = 3034 + self._errHandler.sync(self) + _la = self._input.LA(1) + while True: + self.state = 3032 + self.match(SqlBaseParser.COMMA) + self.state = 3033 + self.identifier() + self.state = 3036 + self._errHandler.sync(self) + _la = self._input.LA(1) + if not (_la==4): + break + + self.state = 3038 + self.match(SqlBaseParser.RIGHT_PAREN) + self.state = 3039 + self.match(SqlBaseParser.ARROW) + self.state = 3040 + self.expression() + pass + + elif la_ == 20: + localctx = SqlBaseParser.ColumnReferenceContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + self.state = 3042 + self.identifier() + pass + + elif la_ == 21: + localctx = SqlBaseParser.ParenthesizedExpressionContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + self.state = 3043 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 3044 + self.expression() + self.state = 3045 + self.match(SqlBaseParser.RIGHT_PAREN) + pass + + elif la_ == 22: + localctx = SqlBaseParser.ExtractContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + self.state = 3047 + self.match(SqlBaseParser.EXTRACT) + self.state = 3048 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 3049 + localctx.field = self.identifier() + self.state = 3050 + self.match(SqlBaseParser.FROM) + self.state = 3051 + localctx.source = self.valueExpression(0) + self.state = 3052 + self.match(SqlBaseParser.RIGHT_PAREN) + pass + + elif la_ == 23: + localctx = SqlBaseParser.SubstringContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + self.state = 3054 + _la = self._input.LA(1) + if not(_la==253 or _la==254): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 3055 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 3056 + localctx.str_ = self.valueExpression(0) + self.state = 3057 + _la = self._input.LA(1) + if not(_la==4 or _la==107): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 3058 + localctx.pos = self.valueExpression(0) + self.state = 3061 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==4 or _la==103: + self.state = 3059 + _la = self._input.LA(1) + if not(_la==4 or _la==103): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 3060 + localctx.len_ = self.valueExpression(0) + + + self.state = 3063 + self.match(SqlBaseParser.RIGHT_PAREN) + pass + + elif la_ == 24: + localctx = SqlBaseParser.TrimContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + self.state = 3065 + self.match(SqlBaseParser.TRIM) + self.state = 3066 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 3068 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,391,self._ctx) + if la_ == 1: + self.state = 3067 + localctx.trimOption = self._input.LT(1) + _la = self._input.LA(1) + if not(_la==25 or _la==140 or _la==272): + localctx.trimOption = self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + + + self.state = 3071 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,392,self._ctx) + if la_ == 1: + self.state = 3070 + localctx.trimStr = self.valueExpression(0) + + + self.state = 3073 + self.match(SqlBaseParser.FROM) + self.state = 3074 + localctx.srcStr = self.valueExpression(0) + self.state = 3075 + self.match(SqlBaseParser.RIGHT_PAREN) + pass + + elif la_ == 25: + localctx = SqlBaseParser.OverlayContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + self.state = 3077 + self.match(SqlBaseParser.OVERLAY) + self.state = 3078 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 3079 + localctx.input_ = self.valueExpression(0) + self.state = 3080 + self.match(SqlBaseParser.PLACING) + self.state = 3081 + localctx.replace = self.valueExpression(0) + self.state = 3082 + self.match(SqlBaseParser.FROM) + self.state = 3083 + localctx.position = self.valueExpression(0) + self.state = 3086 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==103: + self.state = 3084 + self.match(SqlBaseParser.FOR) + self.state = 3085 + localctx.length = self.valueExpression(0) + + + self.state = 3088 + self.match(SqlBaseParser.RIGHT_PAREN) + pass + + elif la_ == 26: + localctx = SqlBaseParser.PercentileContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + self.state = 3090 + localctx.name = self._input.LT(1) + _la = self._input.LA(1) + if not(_la==193 or _la==194): + localctx.name = self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 3091 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 3092 + localctx.percentage = self.valueExpression(0) + self.state = 3093 + self.match(SqlBaseParser.RIGHT_PAREN) + self.state = 3094 + self.match(SqlBaseParser.WITHIN) + self.state = 3095 + self.match(SqlBaseParser.GROUP) + self.state = 3096 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 3097 + self.match(SqlBaseParser.ORDER) + self.state = 3098 + self.match(SqlBaseParser.BY) + self.state = 3099 + self.sortItem() + self.state = 3100 + self.match(SqlBaseParser.RIGHT_PAREN) + self.state = 3107 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,394,self._ctx) + if la_ == 1: + self.state = 3101 + self.match(SqlBaseParser.FILTER) + self.state = 3102 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 3103 + self.match(SqlBaseParser.WHERE) + self.state = 3104 + localctx.where = self.booleanExpression(0) + self.state = 3105 + self.match(SqlBaseParser.RIGHT_PAREN) + + + self.state = 3111 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,395,self._ctx) + if la_ == 1: + self.state = 3109 + self.match(SqlBaseParser.OVER) + self.state = 3110 + self.windowSpec() + + + pass + + + self._ctx.stop = self._input.LT(-1) + self.state = 3125 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,398,self._ctx) + while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt==1: + if self._parseListeners is not None: + self.triggerExitRuleEvent() + _prevctx = localctx + self.state = 3123 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,397,self._ctx) + if la_ == 1: + localctx = SqlBaseParser.SubscriptContext(self, SqlBaseParser.PrimaryExpressionContext(self, _parentctx, _parentState)) + localctx.value = _prevctx + self.pushNewRecursionContext(localctx, _startState, self.RULE_primaryExpression) + self.state = 3115 + if not self.precpred(self._ctx, 9): + from antlr4.error.Errors import FailedPredicateException + raise FailedPredicateException(self, "self.precpred(self._ctx, 9)") + self.state = 3116 + self.match(SqlBaseParser.LEFT_BRACKET) + self.state = 3117 + localctx.index = self.valueExpression(0) + self.state = 3118 + self.match(SqlBaseParser.RIGHT_BRACKET) + pass + + elif la_ == 2: + localctx = SqlBaseParser.DereferenceContext(self, SqlBaseParser.PrimaryExpressionContext(self, _parentctx, _parentState)) + localctx.base = _prevctx + self.pushNewRecursionContext(localctx, _startState, self.RULE_primaryExpression) + self.state = 3120 + if not self.precpred(self._ctx, 7): + from antlr4.error.Errors import FailedPredicateException + raise FailedPredicateException(self, "self.precpred(self._ctx, 7)") + self.state = 3121 + self.match(SqlBaseParser.DOT) + self.state = 3122 + localctx.fieldName = self.identifier() + pass + + + self.state = 3127 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,398,self._ctx) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.unrollRecursionContexts(_parentctx) + return localctx + + + class ConstantContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + + def getRuleIndex(self): + return SqlBaseParser.RULE_constant + + + def copyFrom(self, ctx:ParserRuleContext): + super().copyFrom(ctx) + + + + class NullLiteralContext(ConstantContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.ConstantContext + super().__init__(parser) + self.copyFrom(ctx) + + def NULL(self): + return self.getToken(SqlBaseParser.NULL, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterNullLiteral" ): + listener.enterNullLiteral(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitNullLiteral" ): + listener.exitNullLiteral(self) + + + class StringLiteralContext(ConstantContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.ConstantContext + super().__init__(parser) + self.copyFrom(ctx) + + def stringLit(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.StringLitContext) + else: + return self.getTypedRuleContext(SqlBaseParser.StringLitContext,i) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterStringLiteral" ): + listener.enterStringLiteral(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitStringLiteral" ): + listener.exitStringLiteral(self) + + + class TypeConstructorContext(ConstantContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.ConstantContext + super().__init__(parser) + self.copyFrom(ctx) + + def identifier(self): + return self.getTypedRuleContext(SqlBaseParser.IdentifierContext,0) + + def stringLit(self): + return self.getTypedRuleContext(SqlBaseParser.StringLitContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterTypeConstructor" ): + listener.enterTypeConstructor(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitTypeConstructor" ): + listener.exitTypeConstructor(self) + + + class ParameterLiteralContext(ConstantContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.ConstantContext + super().__init__(parser) + self.copyFrom(ctx) + + def COLON(self): + return self.getToken(SqlBaseParser.COLON, 0) + def identifier(self): + return self.getTypedRuleContext(SqlBaseParser.IdentifierContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterParameterLiteral" ): + listener.enterParameterLiteral(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitParameterLiteral" ): + listener.exitParameterLiteral(self) + + + class IntervalLiteralContext(ConstantContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.ConstantContext + super().__init__(parser) + self.copyFrom(ctx) + + def interval(self): + return self.getTypedRuleContext(SqlBaseParser.IntervalContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterIntervalLiteral" ): + listener.enterIntervalLiteral(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitIntervalLiteral" ): + listener.exitIntervalLiteral(self) + + + class NumericLiteralContext(ConstantContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.ConstantContext + super().__init__(parser) + self.copyFrom(ctx) + + def number(self): + return self.getTypedRuleContext(SqlBaseParser.NumberContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterNumericLiteral" ): + listener.enterNumericLiteral(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitNumericLiteral" ): + listener.exitNumericLiteral(self) + + + class BooleanLiteralContext(ConstantContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.ConstantContext + super().__init__(parser) + self.copyFrom(ctx) + + def booleanValue(self): + return self.getTypedRuleContext(SqlBaseParser.BooleanValueContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterBooleanLiteral" ): + listener.enterBooleanLiteral(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitBooleanLiteral" ): + listener.exitBooleanLiteral(self) + + + + def constant(self): + + localctx = SqlBaseParser.ConstantContext(self, self._ctx, self.state) + self.enterRule(localctx, 250, self.RULE_constant) + try: + self.state = 3142 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,400,self._ctx) + if la_ == 1: + localctx = SqlBaseParser.NullLiteralContext(self, localctx) + self.enterOuterAlt(localctx, 1) + self.state = 3128 + self.match(SqlBaseParser.NULL) + pass + + elif la_ == 2: + localctx = SqlBaseParser.ParameterLiteralContext(self, localctx) + self.enterOuterAlt(localctx, 2) + self.state = 3129 + self.match(SqlBaseParser.COLON) + self.state = 3130 + self.identifier() + pass + + elif la_ == 3: + localctx = SqlBaseParser.IntervalLiteralContext(self, localctx) + self.enterOuterAlt(localctx, 3) + self.state = 3131 + self.interval() + pass + + elif la_ == 4: + localctx = SqlBaseParser.TypeConstructorContext(self, localctx) + self.enterOuterAlt(localctx, 4) + self.state = 3132 + self.identifier() + self.state = 3133 + self.stringLit() + pass + + elif la_ == 5: + localctx = SqlBaseParser.NumericLiteralContext(self, localctx) + self.enterOuterAlt(localctx, 5) + self.state = 3135 + self.number() + pass + + elif la_ == 6: + localctx = SqlBaseParser.BooleanLiteralContext(self, localctx) + self.enterOuterAlt(localctx, 6) + self.state = 3136 + self.booleanValue() + pass + + elif la_ == 7: + localctx = SqlBaseParser.StringLiteralContext(self, localctx) + self.enterOuterAlt(localctx, 7) + self.state = 3138 + self._errHandler.sync(self) + _alt = 1 + while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt == 1: + self.state = 3137 + self.stringLit() + + else: + raise NoViableAltException(self) + self.state = 3140 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,399,self._ctx) + + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class ComparisonOperatorContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def EQ(self): + return self.getToken(SqlBaseParser.EQ, 0) + + def NEQ(self): + return self.getToken(SqlBaseParser.NEQ, 0) + + def NEQJ(self): + return self.getToken(SqlBaseParser.NEQJ, 0) + + def LT(self): + return self.getToken(SqlBaseParser.LT, 0) + + def LTE(self): + return self.getToken(SqlBaseParser.LTE, 0) + + def GT(self): + return self.getToken(SqlBaseParser.GT, 0) + + def GTE(self): + return self.getToken(SqlBaseParser.GTE, 0) + + def NSEQ(self): + return self.getToken(SqlBaseParser.NSEQ, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_comparisonOperator + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterComparisonOperator" ): + listener.enterComparisonOperator(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitComparisonOperator" ): + listener.exitComparisonOperator(self) + + + + + def comparisonOperator(self): + + localctx = SqlBaseParser.ComparisonOperatorContext(self, self._ctx, self.state) + self.enterRule(localctx, 252, self.RULE_comparisonOperator) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 3144 + _la = self._input.LA(1) + if not(((((_la - 308)) & ~0x3f) == 0 and ((1 << (_la - 308)) & 255) != 0)): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class ArithmeticOperatorContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def PLUS(self): + return self.getToken(SqlBaseParser.PLUS, 0) + + def MINUS(self): + return self.getToken(SqlBaseParser.MINUS, 0) + + def ASTERISK(self): + return self.getToken(SqlBaseParser.ASTERISK, 0) + + def SLASH(self): + return self.getToken(SqlBaseParser.SLASH, 0) + + def PERCENT(self): + return self.getToken(SqlBaseParser.PERCENT, 0) + + def DIV(self): + return self.getToken(SqlBaseParser.DIV, 0) + + def TILDE(self): + return self.getToken(SqlBaseParser.TILDE, 0) + + def AMPERSAND(self): + return self.getToken(SqlBaseParser.AMPERSAND, 0) + + def PIPE(self): + return self.getToken(SqlBaseParser.PIPE, 0) + + def CONCAT_PIPE(self): + return self.getToken(SqlBaseParser.CONCAT_PIPE, 0) + + def HAT(self): + return self.getToken(SqlBaseParser.HAT, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_arithmeticOperator + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterArithmeticOperator" ): + listener.enterArithmeticOperator(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitArithmeticOperator" ): + listener.exitArithmeticOperator(self) + + + + + def arithmeticOperator(self): + + localctx = SqlBaseParser.ArithmeticOperatorContext(self, self._ctx, self.state) + self.enterRule(localctx, 254, self.RULE_arithmeticOperator) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 3146 + _la = self._input.LA(1) + if not(_la==81 or ((((_la - 316)) & ~0x3f) == 0 and ((1 << (_la - 316)) & 1023) != 0)): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class PredicateOperatorContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def OR(self): + return self.getToken(SqlBaseParser.OR, 0) + + def AND(self): + return self.getToken(SqlBaseParser.AND, 0) + + def IN(self): + return self.getToken(SqlBaseParser.IN, 0) + + def NOT(self): + return self.getToken(SqlBaseParser.NOT, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_predicateOperator + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterPredicateOperator" ): + listener.enterPredicateOperator(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitPredicateOperator" ): + listener.exitPredicateOperator(self) + + + + + def predicateOperator(self): + + localctx = SqlBaseParser.PredicateOperatorContext(self, self._ctx, self.state) + self.enterRule(localctx, 256, self.RULE_predicateOperator) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 3148 + _la = self._input.LA(1) + if not(_la==14 or ((((_la - 122)) & ~0x3f) == 0 and ((1 << (_la - 122)) & 577586652210266113) != 0)): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class BooleanValueContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def TRUE(self): + return self.getToken(SqlBaseParser.TRUE, 0) + + def FALSE(self): + return self.getToken(SqlBaseParser.FALSE, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_booleanValue + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterBooleanValue" ): + listener.enterBooleanValue(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitBooleanValue" ): + listener.exitBooleanValue(self) + + + + + def booleanValue(self): + + localctx = SqlBaseParser.BooleanValueContext(self, self._ctx, self.state) + self.enterRule(localctx, 258, self.RULE_booleanValue) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 3150 + _la = self._input.LA(1) + if not(_la==96 or _la==277): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class IntervalContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def INTERVAL(self): + return self.getToken(SqlBaseParser.INTERVAL, 0) + + def errorCapturingMultiUnitsInterval(self): + return self.getTypedRuleContext(SqlBaseParser.ErrorCapturingMultiUnitsIntervalContext,0) + + + def errorCapturingUnitToUnitInterval(self): + return self.getTypedRuleContext(SqlBaseParser.ErrorCapturingUnitToUnitIntervalContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_interval + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterInterval" ): + listener.enterInterval(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitInterval" ): + listener.exitInterval(self) + + + + + def interval(self): + + localctx = SqlBaseParser.IntervalContext(self, self._ctx, self.state) + self.enterRule(localctx, 260, self.RULE_interval) + try: + self.enterOuterAlt(localctx, 1) + self.state = 3152 + self.match(SqlBaseParser.INTERVAL) + self.state = 3155 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,401,self._ctx) + if la_ == 1: + self.state = 3153 + self.errorCapturingMultiUnitsInterval() + pass + + elif la_ == 2: + self.state = 3154 + self.errorCapturingUnitToUnitInterval() + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class ErrorCapturingMultiUnitsIntervalContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self.body = None # MultiUnitsIntervalContext + + def multiUnitsInterval(self): + return self.getTypedRuleContext(SqlBaseParser.MultiUnitsIntervalContext,0) + + + def unitToUnitInterval(self): + return self.getTypedRuleContext(SqlBaseParser.UnitToUnitIntervalContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_errorCapturingMultiUnitsInterval + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterErrorCapturingMultiUnitsInterval" ): + listener.enterErrorCapturingMultiUnitsInterval(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitErrorCapturingMultiUnitsInterval" ): + listener.exitErrorCapturingMultiUnitsInterval(self) + + + + + def errorCapturingMultiUnitsInterval(self): + + localctx = SqlBaseParser.ErrorCapturingMultiUnitsIntervalContext(self, self._ctx, self.state) + self.enterRule(localctx, 262, self.RULE_errorCapturingMultiUnitsInterval) + try: + self.enterOuterAlt(localctx, 1) + self.state = 3157 + localctx.body = self.multiUnitsInterval() + self.state = 3159 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,402,self._ctx) + if la_ == 1: + self.state = 3158 + self.unitToUnitInterval() + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class MultiUnitsIntervalContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self._unitInMultiUnits = None # UnitInMultiUnitsContext + self.unit = list() # of UnitInMultiUnitsContexts + + def intervalValue(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.IntervalValueContext) + else: + return self.getTypedRuleContext(SqlBaseParser.IntervalValueContext,i) + + + def unitInMultiUnits(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.UnitInMultiUnitsContext) + else: + return self.getTypedRuleContext(SqlBaseParser.UnitInMultiUnitsContext,i) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_multiUnitsInterval + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterMultiUnitsInterval" ): + listener.enterMultiUnitsInterval(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitMultiUnitsInterval" ): + listener.exitMultiUnitsInterval(self) + + + + + def multiUnitsInterval(self): + + localctx = SqlBaseParser.MultiUnitsIntervalContext(self, self._ctx, self.state) + self.enterRule(localctx, 264, self.RULE_multiUnitsInterval) + try: + self.enterOuterAlt(localctx, 1) + self.state = 3164 + self._errHandler.sync(self) + _alt = 1 + while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt == 1: + self.state = 3161 + self.intervalValue() + self.state = 3162 + localctx._unitInMultiUnits = self.unitInMultiUnits() + localctx.unit.append(localctx._unitInMultiUnits) + + else: + raise NoViableAltException(self) + self.state = 3166 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,403,self._ctx) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class ErrorCapturingUnitToUnitIntervalContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self.body = None # UnitToUnitIntervalContext + self.error1 = None # MultiUnitsIntervalContext + self.error2 = None # UnitToUnitIntervalContext + + def unitToUnitInterval(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.UnitToUnitIntervalContext) + else: + return self.getTypedRuleContext(SqlBaseParser.UnitToUnitIntervalContext,i) + + + def multiUnitsInterval(self): + return self.getTypedRuleContext(SqlBaseParser.MultiUnitsIntervalContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_errorCapturingUnitToUnitInterval + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterErrorCapturingUnitToUnitInterval" ): + listener.enterErrorCapturingUnitToUnitInterval(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitErrorCapturingUnitToUnitInterval" ): + listener.exitErrorCapturingUnitToUnitInterval(self) + + + + + def errorCapturingUnitToUnitInterval(self): + + localctx = SqlBaseParser.ErrorCapturingUnitToUnitIntervalContext(self, self._ctx, self.state) + self.enterRule(localctx, 266, self.RULE_errorCapturingUnitToUnitInterval) + try: + self.enterOuterAlt(localctx, 1) + self.state = 3168 + localctx.body = self.unitToUnitInterval() + self.state = 3171 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,404,self._ctx) + if la_ == 1: + self.state = 3169 + localctx.error1 = self.multiUnitsInterval() + + elif la_ == 2: + self.state = 3170 + localctx.error2 = self.unitToUnitInterval() + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class UnitToUnitIntervalContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self.value = None # IntervalValueContext + self.from_ = None # UnitInUnitToUnitContext + self.to = None # UnitInUnitToUnitContext + + def TO(self): + return self.getToken(SqlBaseParser.TO, 0) + + def intervalValue(self): + return self.getTypedRuleContext(SqlBaseParser.IntervalValueContext,0) + + + def unitInUnitToUnit(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.UnitInUnitToUnitContext) + else: + return self.getTypedRuleContext(SqlBaseParser.UnitInUnitToUnitContext,i) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_unitToUnitInterval + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterUnitToUnitInterval" ): + listener.enterUnitToUnitInterval(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitUnitToUnitInterval" ): + listener.exitUnitToUnitInterval(self) + + + + + def unitToUnitInterval(self): + + localctx = SqlBaseParser.UnitToUnitIntervalContext(self, self._ctx, self.state) + self.enterRule(localctx, 268, self.RULE_unitToUnitInterval) + try: + self.enterOuterAlt(localctx, 1) + self.state = 3173 + localctx.value = self.intervalValue() + self.state = 3174 + localctx.from_ = self.unitInUnitToUnit() + self.state = 3175 + self.match(SqlBaseParser.TO) + self.state = 3176 + localctx.to = self.unitInUnitToUnit() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class IntervalValueContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def INTEGER_VALUE(self): + return self.getToken(SqlBaseParser.INTEGER_VALUE, 0) + + def DECIMAL_VALUE(self): + return self.getToken(SqlBaseParser.DECIMAL_VALUE, 0) + + def stringLit(self): + return self.getTypedRuleContext(SqlBaseParser.StringLitContext,0) + + + def PLUS(self): + return self.getToken(SqlBaseParser.PLUS, 0) + + def MINUS(self): + return self.getToken(SqlBaseParser.MINUS, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_intervalValue + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterIntervalValue" ): + listener.enterIntervalValue(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitIntervalValue" ): + listener.exitIntervalValue(self) + + + + + def intervalValue(self): + + localctx = SqlBaseParser.IntervalValueContext(self, self._ctx, self.state) + self.enterRule(localctx, 270, self.RULE_intervalValue) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 3179 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==316 or _la==317: + self.state = 3178 + _la = self._input.LA(1) + if not(_la==316 or _la==317): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + + + self.state = 3184 + self._errHandler.sync(self) + token = self._input.LA(1) + if token in [335]: + self.state = 3181 + self.match(SqlBaseParser.INTEGER_VALUE) + pass + elif token in [337]: + self.state = 3182 + self.match(SqlBaseParser.DECIMAL_VALUE) + pass + elif token in [330, 331]: + self.state = 3183 + self.stringLit() + pass + else: + raise NoViableAltException(self) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class UnitInMultiUnitsContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def NANOSECOND(self): + return self.getToken(SqlBaseParser.NANOSECOND, 0) + + def NANOSECONDS(self): + return self.getToken(SqlBaseParser.NANOSECONDS, 0) + + def MICROSECOND(self): + return self.getToken(SqlBaseParser.MICROSECOND, 0) + + def MICROSECONDS(self): + return self.getToken(SqlBaseParser.MICROSECONDS, 0) + + def MILLISECOND(self): + return self.getToken(SqlBaseParser.MILLISECOND, 0) + + def MILLISECONDS(self): + return self.getToken(SqlBaseParser.MILLISECONDS, 0) + + def SECOND(self): + return self.getToken(SqlBaseParser.SECOND, 0) + + def SECONDS(self): + return self.getToken(SqlBaseParser.SECONDS, 0) + + def MINUTE(self): + return self.getToken(SqlBaseParser.MINUTE, 0) + + def MINUTES(self): + return self.getToken(SqlBaseParser.MINUTES, 0) + + def HOUR(self): + return self.getToken(SqlBaseParser.HOUR, 0) + + def HOURS(self): + return self.getToken(SqlBaseParser.HOURS, 0) + + def DAY(self): + return self.getToken(SqlBaseParser.DAY, 0) + + def DAYS(self): + return self.getToken(SqlBaseParser.DAYS, 0) + + def WEEK(self): + return self.getToken(SqlBaseParser.WEEK, 0) + + def WEEKS(self): + return self.getToken(SqlBaseParser.WEEKS, 0) + + def MONTH(self): + return self.getToken(SqlBaseParser.MONTH, 0) + + def MONTHS(self): + return self.getToken(SqlBaseParser.MONTHS, 0) + + def YEAR(self): + return self.getToken(SqlBaseParser.YEAR, 0) + + def YEARS(self): + return self.getToken(SqlBaseParser.YEARS, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_unitInMultiUnits + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterUnitInMultiUnits" ): + listener.enterUnitInMultiUnits(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitUnitInMultiUnits" ): + listener.exitUnitInMultiUnits(self) + + + + + def unitInMultiUnits(self): + + localctx = SqlBaseParser.UnitInMultiUnitsContext(self, self._ctx, self.state) + self.enterRule(localctx, 272, self.RULE_unitInMultiUnits) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 3186 + _la = self._input.LA(1) + if not(_la==61 or _la==62 or ((((_la - 117)) & ~0x3f) == 0 and ((1 << (_la - 117)) & 7035774906138627) != 0) or _la==229 or _la==230 or ((((_la - 298)) & ~0x3f) == 0 and ((1 << (_la - 298)) & 387) != 0)): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class UnitInUnitToUnitContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def SECOND(self): + return self.getToken(SqlBaseParser.SECOND, 0) + + def MINUTE(self): + return self.getToken(SqlBaseParser.MINUTE, 0) + + def HOUR(self): + return self.getToken(SqlBaseParser.HOUR, 0) + + def DAY(self): + return self.getToken(SqlBaseParser.DAY, 0) + + def MONTH(self): + return self.getToken(SqlBaseParser.MONTH, 0) + + def YEAR(self): + return self.getToken(SqlBaseParser.YEAR, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_unitInUnitToUnit + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterUnitInUnitToUnit" ): + listener.enterUnitInUnitToUnit(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitUnitInUnitToUnit" ): + listener.exitUnitInUnitToUnit(self) + + + + + def unitInUnitToUnit(self): + + localctx = SqlBaseParser.UnitInUnitToUnitContext(self, self._ctx, self.state) + self.enterRule(localctx, 274, self.RULE_unitInUnitToUnit) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 3188 + _la = self._input.LA(1) + if not(_la==61 or ((((_la - 117)) & ~0x3f) == 0 and ((1 << (_la - 117)) & 87960930222081) != 0) or _la==229 or _la==305): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class ColPositionContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self.position = None # Token + self.afterCol = None # ErrorCapturingIdentifierContext + + def FIRST(self): + return self.getToken(SqlBaseParser.FIRST, 0) + + def AFTER(self): + return self.getToken(SqlBaseParser.AFTER, 0) + + def errorCapturingIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.ErrorCapturingIdentifierContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_colPosition + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterColPosition" ): + listener.enterColPosition(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitColPosition" ): + listener.exitColPosition(self) + + + + + def colPosition(self): + + localctx = SqlBaseParser.ColPositionContext(self, self._ctx, self.state) + self.enterRule(localctx, 276, self.RULE_colPosition) + try: + self.state = 3193 + self._errHandler.sync(self) + token = self._input.LA(1) + if token in [101]: + self.enterOuterAlt(localctx, 1) + self.state = 3190 + localctx.position = self.match(SqlBaseParser.FIRST) + pass + elif token in [9]: + self.enterOuterAlt(localctx, 2) + self.state = 3191 + localctx.position = self.match(SqlBaseParser.AFTER) + self.state = 3192 + localctx.afterCol = self.errorCapturingIdentifier() + pass + else: + raise NoViableAltException(self) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class DataTypeContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + + def getRuleIndex(self): + return SqlBaseParser.RULE_dataType + + + def copyFrom(self, ctx:ParserRuleContext): + super().copyFrom(ctx) + + + + class ComplexDataTypeContext(DataTypeContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.DataTypeContext + super().__init__(parser) + self.complex_ = None # Token + self.copyFrom(ctx) + + def LT(self): + return self.getToken(SqlBaseParser.LT, 0) + def dataType(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.DataTypeContext) + else: + return self.getTypedRuleContext(SqlBaseParser.DataTypeContext,i) + + def GT(self): + return self.getToken(SqlBaseParser.GT, 0) + def ARRAY(self): + return self.getToken(SqlBaseParser.ARRAY, 0) + def COMMA(self): + return self.getToken(SqlBaseParser.COMMA, 0) + def MAP(self): + return self.getToken(SqlBaseParser.MAP, 0) + def STRUCT(self): + return self.getToken(SqlBaseParser.STRUCT, 0) + def NEQ(self): + return self.getToken(SqlBaseParser.NEQ, 0) + def complexColTypeList(self): + return self.getTypedRuleContext(SqlBaseParser.ComplexColTypeListContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterComplexDataType" ): + listener.enterComplexDataType(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitComplexDataType" ): + listener.exitComplexDataType(self) + + + class YearMonthIntervalDataTypeContext(DataTypeContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.DataTypeContext + super().__init__(parser) + self.from_ = None # Token + self.to = None # Token + self.copyFrom(ctx) + + def INTERVAL(self): + return self.getToken(SqlBaseParser.INTERVAL, 0) + def YEAR(self): + return self.getToken(SqlBaseParser.YEAR, 0) + def MONTH(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.MONTH) + else: + return self.getToken(SqlBaseParser.MONTH, i) + def TO(self): + return self.getToken(SqlBaseParser.TO, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterYearMonthIntervalDataType" ): + listener.enterYearMonthIntervalDataType(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitYearMonthIntervalDataType" ): + listener.exitYearMonthIntervalDataType(self) + + + class DayTimeIntervalDataTypeContext(DataTypeContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.DataTypeContext + super().__init__(parser) + self.from_ = None # Token + self.to = None # Token + self.copyFrom(ctx) + + def INTERVAL(self): + return self.getToken(SqlBaseParser.INTERVAL, 0) + def DAY(self): + return self.getToken(SqlBaseParser.DAY, 0) + def HOUR(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.HOUR) + else: + return self.getToken(SqlBaseParser.HOUR, i) + def MINUTE(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.MINUTE) + else: + return self.getToken(SqlBaseParser.MINUTE, i) + def SECOND(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.SECOND) + else: + return self.getToken(SqlBaseParser.SECOND, i) + def TO(self): + return self.getToken(SqlBaseParser.TO, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterDayTimeIntervalDataType" ): + listener.enterDayTimeIntervalDataType(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitDayTimeIntervalDataType" ): + listener.exitDayTimeIntervalDataType(self) + + + class PrimitiveDataTypeContext(DataTypeContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.DataTypeContext + super().__init__(parser) + self.copyFrom(ctx) + + def identifier(self): + return self.getTypedRuleContext(SqlBaseParser.IdentifierContext,0) + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + def INTEGER_VALUE(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.INTEGER_VALUE) + else: + return self.getToken(SqlBaseParser.INTEGER_VALUE, i) + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterPrimitiveDataType" ): + listener.enterPrimitiveDataType(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitPrimitiveDataType" ): + listener.exitPrimitiveDataType(self) + + + + def dataType(self): + + localctx = SqlBaseParser.DataTypeContext(self, self._ctx, self.state) + self.enterRule(localctx, 278, self.RULE_dataType) + self._la = 0 # Token type + try: + self.state = 3241 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,414,self._ctx) + if la_ == 1: + localctx = SqlBaseParser.ComplexDataTypeContext(self, localctx) + self.enterOuterAlt(localctx, 1) + self.state = 3195 + localctx.complex_ = self.match(SqlBaseParser.ARRAY) + self.state = 3196 + self.match(SqlBaseParser.LT) + self.state = 3197 + self.dataType() + self.state = 3198 + self.match(SqlBaseParser.GT) + pass + + elif la_ == 2: + localctx = SqlBaseParser.ComplexDataTypeContext(self, localctx) + self.enterOuterAlt(localctx, 2) + self.state = 3200 + localctx.complex_ = self.match(SqlBaseParser.MAP) + self.state = 3201 + self.match(SqlBaseParser.LT) + self.state = 3202 + self.dataType() + self.state = 3203 + self.match(SqlBaseParser.COMMA) + self.state = 3204 + self.dataType() + self.state = 3205 + self.match(SqlBaseParser.GT) + pass + + elif la_ == 3: + localctx = SqlBaseParser.ComplexDataTypeContext(self, localctx) + self.enterOuterAlt(localctx, 3) + self.state = 3207 + localctx.complex_ = self.match(SqlBaseParser.STRUCT) + self.state = 3214 + self._errHandler.sync(self) + token = self._input.LA(1) + if token in [312]: + self.state = 3208 + self.match(SqlBaseParser.LT) + self.state = 3210 + self._errHandler.sync(self) + _la = self._input.LA(1) + if (((_la) & ~0x3f) == 0 and ((1 << _la) & -256) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & -1) != 0) or ((((_la - 128)) & ~0x3f) == 0 and ((1 << (_la - 128)) & -1) != 0) or ((((_la - 192)) & ~0x3f) == 0 and ((1 << (_la - 192)) & -1) != 0) or ((((_la - 256)) & ~0x3f) == 0 and ((1 << (_la - 256)) & 4503599627370495) != 0) or ((((_la - 331)) & ~0x3f) == 0 and ((1 << (_la - 331)) & 3073) != 0): + self.state = 3209 + self.complexColTypeList() + + + self.state = 3212 + self.match(SqlBaseParser.GT) + pass + elif token in [310]: + self.state = 3213 + self.match(SqlBaseParser.NEQ) + pass + else: + raise NoViableAltException(self) + + pass + + elif la_ == 4: + localctx = SqlBaseParser.YearMonthIntervalDataTypeContext(self, localctx) + self.enterOuterAlt(localctx, 4) + self.state = 3216 + self.match(SqlBaseParser.INTERVAL) + self.state = 3217 + localctx.from_ = self._input.LT(1) + _la = self._input.LA(1) + if not(_la==163 or _la==305): + localctx.from_ = self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 3220 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,410,self._ctx) + if la_ == 1: + self.state = 3218 + self.match(SqlBaseParser.TO) + self.state = 3219 + localctx.to = self.match(SqlBaseParser.MONTH) + + + pass + + elif la_ == 5: + localctx = SqlBaseParser.DayTimeIntervalDataTypeContext(self, localctx) + self.enterOuterAlt(localctx, 5) + self.state = 3222 + self.match(SqlBaseParser.INTERVAL) + self.state = 3223 + localctx.from_ = self._input.LT(1) + _la = self._input.LA(1) + if not(_la==61 or _la==117 or _la==161 or _la==229): + localctx.from_ = self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 3226 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,411,self._ctx) + if la_ == 1: + self.state = 3224 + self.match(SqlBaseParser.TO) + self.state = 3225 + localctx.to = self._input.LT(1) + _la = self._input.LA(1) + if not(_la==117 or _la==161 or _la==229): + localctx.to = self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + + + pass + + elif la_ == 6: + localctx = SqlBaseParser.PrimitiveDataTypeContext(self, localctx) + self.enterOuterAlt(localctx, 6) + self.state = 3228 + self.identifier() + self.state = 3239 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,413,self._ctx) + if la_ == 1: + self.state = 3229 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 3230 + self.match(SqlBaseParser.INTEGER_VALUE) + self.state = 3235 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 3231 + self.match(SqlBaseParser.COMMA) + self.state = 3232 + self.match(SqlBaseParser.INTEGER_VALUE) + self.state = 3237 + self._errHandler.sync(self) + _la = self._input.LA(1) + + self.state = 3238 + self.match(SqlBaseParser.RIGHT_PAREN) + + + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class QualifiedColTypeWithPositionListContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def qualifiedColTypeWithPosition(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.QualifiedColTypeWithPositionContext) + else: + return self.getTypedRuleContext(SqlBaseParser.QualifiedColTypeWithPositionContext,i) + + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def getRuleIndex(self): + return SqlBaseParser.RULE_qualifiedColTypeWithPositionList + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterQualifiedColTypeWithPositionList" ): + listener.enterQualifiedColTypeWithPositionList(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitQualifiedColTypeWithPositionList" ): + listener.exitQualifiedColTypeWithPositionList(self) + + + + + def qualifiedColTypeWithPositionList(self): + + localctx = SqlBaseParser.QualifiedColTypeWithPositionListContext(self, self._ctx, self.state) + self.enterRule(localctx, 280, self.RULE_qualifiedColTypeWithPositionList) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 3243 + self.qualifiedColTypeWithPosition() + self.state = 3248 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 3244 + self.match(SqlBaseParser.COMMA) + self.state = 3245 + self.qualifiedColTypeWithPosition() + self.state = 3250 + self._errHandler.sync(self) + _la = self._input.LA(1) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class QualifiedColTypeWithPositionContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self.name = None # MultipartIdentifierContext + + def dataType(self): + return self.getTypedRuleContext(SqlBaseParser.DataTypeContext,0) + + + def multipartIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.MultipartIdentifierContext,0) + + + def colDefinitionDescriptorWithPosition(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.ColDefinitionDescriptorWithPositionContext) + else: + return self.getTypedRuleContext(SqlBaseParser.ColDefinitionDescriptorWithPositionContext,i) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_qualifiedColTypeWithPosition + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterQualifiedColTypeWithPosition" ): + listener.enterQualifiedColTypeWithPosition(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitQualifiedColTypeWithPosition" ): + listener.exitQualifiedColTypeWithPosition(self) + + + + + def qualifiedColTypeWithPosition(self): + + localctx = SqlBaseParser.QualifiedColTypeWithPositionContext(self, self._ctx, self.state) + self.enterRule(localctx, 282, self.RULE_qualifiedColTypeWithPosition) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 3251 + localctx.name = self.multipartIdentifier() + self.state = 3252 + self.dataType() + self.state = 3256 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==9 or _la==45 or _la==70 or _la==101 or _la==172: + self.state = 3253 + self.colDefinitionDescriptorWithPosition() + self.state = 3258 + self._errHandler.sync(self) + _la = self._input.LA(1) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class ColDefinitionDescriptorWithPositionContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def NOT(self): + return self.getToken(SqlBaseParser.NOT, 0) + + def NULL(self): + return self.getToken(SqlBaseParser.NULL, 0) + + def defaultExpression(self): + return self.getTypedRuleContext(SqlBaseParser.DefaultExpressionContext,0) + + + def commentSpec(self): + return self.getTypedRuleContext(SqlBaseParser.CommentSpecContext,0) + + + def colPosition(self): + return self.getTypedRuleContext(SqlBaseParser.ColPositionContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_colDefinitionDescriptorWithPosition + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterColDefinitionDescriptorWithPosition" ): + listener.enterColDefinitionDescriptorWithPosition(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitColDefinitionDescriptorWithPosition" ): + listener.exitColDefinitionDescriptorWithPosition(self) + + + + + def colDefinitionDescriptorWithPosition(self): + + localctx = SqlBaseParser.ColDefinitionDescriptorWithPositionContext(self, self._ctx, self.state) + self.enterRule(localctx, 284, self.RULE_colDefinitionDescriptorWithPosition) + try: + self.state = 3264 + self._errHandler.sync(self) + token = self._input.LA(1) + if token in [172]: + self.enterOuterAlt(localctx, 1) + self.state = 3259 + self.match(SqlBaseParser.NOT) + self.state = 3260 + self.match(SqlBaseParser.NULL) + pass + elif token in [70]: + self.enterOuterAlt(localctx, 2) + self.state = 3261 + self.defaultExpression() + pass + elif token in [45]: + self.enterOuterAlt(localctx, 3) + self.state = 3262 + self.commentSpec() + pass + elif token in [9, 101]: + self.enterOuterAlt(localctx, 4) + self.state = 3263 + self.colPosition() + pass + else: + raise NoViableAltException(self) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class DefaultExpressionContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def DEFAULT(self): + return self.getToken(SqlBaseParser.DEFAULT, 0) + + def expression(self): + return self.getTypedRuleContext(SqlBaseParser.ExpressionContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_defaultExpression + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterDefaultExpression" ): + listener.enterDefaultExpression(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitDefaultExpression" ): + listener.exitDefaultExpression(self) + + + + + def defaultExpression(self): + + localctx = SqlBaseParser.DefaultExpressionContext(self, self._ctx, self.state) + self.enterRule(localctx, 286, self.RULE_defaultExpression) + try: + self.enterOuterAlt(localctx, 1) + self.state = 3266 + self.match(SqlBaseParser.DEFAULT) + self.state = 3267 + self.expression() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class ColTypeListContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def colType(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.ColTypeContext) + else: + return self.getTypedRuleContext(SqlBaseParser.ColTypeContext,i) + + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def getRuleIndex(self): + return SqlBaseParser.RULE_colTypeList + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterColTypeList" ): + listener.enterColTypeList(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitColTypeList" ): + listener.exitColTypeList(self) + + + + + def colTypeList(self): + + localctx = SqlBaseParser.ColTypeListContext(self, self._ctx, self.state) + self.enterRule(localctx, 288, self.RULE_colTypeList) + try: + self.enterOuterAlt(localctx, 1) + self.state = 3269 + self.colType() + self.state = 3274 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,418,self._ctx) + while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt==1: + self.state = 3270 + self.match(SqlBaseParser.COMMA) + self.state = 3271 + self.colType() + self.state = 3276 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,418,self._ctx) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class ColTypeContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self.colName = None # ErrorCapturingIdentifierContext + + def dataType(self): + return self.getTypedRuleContext(SqlBaseParser.DataTypeContext,0) + + + def errorCapturingIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.ErrorCapturingIdentifierContext,0) + + + def NOT(self): + return self.getToken(SqlBaseParser.NOT, 0) + + def NULL(self): + return self.getToken(SqlBaseParser.NULL, 0) + + def commentSpec(self): + return self.getTypedRuleContext(SqlBaseParser.CommentSpecContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_colType + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterColType" ): + listener.enterColType(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitColType" ): + listener.exitColType(self) + + + + + def colType(self): + + localctx = SqlBaseParser.ColTypeContext(self, self._ctx, self.state) + self.enterRule(localctx, 290, self.RULE_colType) + try: + self.enterOuterAlt(localctx, 1) + self.state = 3277 + localctx.colName = self.errorCapturingIdentifier() + self.state = 3278 + self.dataType() + self.state = 3281 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,419,self._ctx) + if la_ == 1: + self.state = 3279 + self.match(SqlBaseParser.NOT) + self.state = 3280 + self.match(SqlBaseParser.NULL) + + + self.state = 3284 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,420,self._ctx) + if la_ == 1: + self.state = 3283 + self.commentSpec() + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class CreateOrReplaceTableColTypeListContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def createOrReplaceTableColType(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.CreateOrReplaceTableColTypeContext) + else: + return self.getTypedRuleContext(SqlBaseParser.CreateOrReplaceTableColTypeContext,i) + + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def getRuleIndex(self): + return SqlBaseParser.RULE_createOrReplaceTableColTypeList + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterCreateOrReplaceTableColTypeList" ): + listener.enterCreateOrReplaceTableColTypeList(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitCreateOrReplaceTableColTypeList" ): + listener.exitCreateOrReplaceTableColTypeList(self) + + + + + def createOrReplaceTableColTypeList(self): + + localctx = SqlBaseParser.CreateOrReplaceTableColTypeListContext(self, self._ctx, self.state) + self.enterRule(localctx, 292, self.RULE_createOrReplaceTableColTypeList) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 3286 + self.createOrReplaceTableColType() + self.state = 3291 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 3287 + self.match(SqlBaseParser.COMMA) + self.state = 3288 + self.createOrReplaceTableColType() + self.state = 3293 + self._errHandler.sync(self) + _la = self._input.LA(1) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class CreateOrReplaceTableColTypeContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self.colName = None # ErrorCapturingIdentifierContext + + def dataType(self): + return self.getTypedRuleContext(SqlBaseParser.DataTypeContext,0) + + + def errorCapturingIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.ErrorCapturingIdentifierContext,0) + + + def colDefinitionOption(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.ColDefinitionOptionContext) + else: + return self.getTypedRuleContext(SqlBaseParser.ColDefinitionOptionContext,i) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_createOrReplaceTableColType + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterCreateOrReplaceTableColType" ): + listener.enterCreateOrReplaceTableColType(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitCreateOrReplaceTableColType" ): + listener.exitCreateOrReplaceTableColType(self) + + + + + def createOrReplaceTableColType(self): + + localctx = SqlBaseParser.CreateOrReplaceTableColTypeContext(self, self._ctx, self.state) + self.enterRule(localctx, 294, self.RULE_createOrReplaceTableColType) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 3294 + localctx.colName = self.errorCapturingIdentifier() + self.state = 3295 + self.dataType() + self.state = 3299 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==45 or _la==70 or _la==111 or _la==172: + self.state = 3296 + self.colDefinitionOption() + self.state = 3301 + self._errHandler.sync(self) + _la = self._input.LA(1) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class ColDefinitionOptionContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def NOT(self): + return self.getToken(SqlBaseParser.NOT, 0) + + def NULL(self): + return self.getToken(SqlBaseParser.NULL, 0) + + def defaultExpression(self): + return self.getTypedRuleContext(SqlBaseParser.DefaultExpressionContext,0) + + + def generationExpression(self): + return self.getTypedRuleContext(SqlBaseParser.GenerationExpressionContext,0) + + + def commentSpec(self): + return self.getTypedRuleContext(SqlBaseParser.CommentSpecContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_colDefinitionOption + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterColDefinitionOption" ): + listener.enterColDefinitionOption(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitColDefinitionOption" ): + listener.exitColDefinitionOption(self) + + + + + def colDefinitionOption(self): + + localctx = SqlBaseParser.ColDefinitionOptionContext(self, self._ctx, self.state) + self.enterRule(localctx, 296, self.RULE_colDefinitionOption) + try: + self.state = 3307 + self._errHandler.sync(self) + token = self._input.LA(1) + if token in [172]: + self.enterOuterAlt(localctx, 1) + self.state = 3302 + self.match(SqlBaseParser.NOT) + self.state = 3303 + self.match(SqlBaseParser.NULL) + pass + elif token in [70]: + self.enterOuterAlt(localctx, 2) + self.state = 3304 + self.defaultExpression() + pass + elif token in [111]: + self.enterOuterAlt(localctx, 3) + self.state = 3305 + self.generationExpression() + pass + elif token in [45]: + self.enterOuterAlt(localctx, 4) + self.state = 3306 + self.commentSpec() + pass + else: + raise NoViableAltException(self) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class GenerationExpressionContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def GENERATED(self): + return self.getToken(SqlBaseParser.GENERATED, 0) + + def ALWAYS(self): + return self.getToken(SqlBaseParser.ALWAYS, 0) + + def AS(self): + return self.getToken(SqlBaseParser.AS, 0) + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + + def expression(self): + return self.getTypedRuleContext(SqlBaseParser.ExpressionContext,0) + + + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_generationExpression + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterGenerationExpression" ): + listener.enterGenerationExpression(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitGenerationExpression" ): + listener.exitGenerationExpression(self) + + + + + def generationExpression(self): + + localctx = SqlBaseParser.GenerationExpressionContext(self, self._ctx, self.state) + self.enterRule(localctx, 298, self.RULE_generationExpression) + try: + self.enterOuterAlt(localctx, 1) + self.state = 3309 + self.match(SqlBaseParser.GENERATED) + self.state = 3310 + self.match(SqlBaseParser.ALWAYS) + self.state = 3311 + self.match(SqlBaseParser.AS) + self.state = 3312 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 3313 + self.expression() + self.state = 3314 + self.match(SqlBaseParser.RIGHT_PAREN) + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class ComplexColTypeListContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def complexColType(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.ComplexColTypeContext) + else: + return self.getTypedRuleContext(SqlBaseParser.ComplexColTypeContext,i) + + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def getRuleIndex(self): + return SqlBaseParser.RULE_complexColTypeList + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterComplexColTypeList" ): + listener.enterComplexColTypeList(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitComplexColTypeList" ): + listener.exitComplexColTypeList(self) + + + + + def complexColTypeList(self): + + localctx = SqlBaseParser.ComplexColTypeListContext(self, self._ctx, self.state) + self.enterRule(localctx, 300, self.RULE_complexColTypeList) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 3316 + self.complexColType() + self.state = 3321 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 3317 + self.match(SqlBaseParser.COMMA) + self.state = 3318 + self.complexColType() + self.state = 3323 + self._errHandler.sync(self) + _la = self._input.LA(1) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class ComplexColTypeContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def identifier(self): + return self.getTypedRuleContext(SqlBaseParser.IdentifierContext,0) + + + def dataType(self): + return self.getTypedRuleContext(SqlBaseParser.DataTypeContext,0) + + + def COLON(self): + return self.getToken(SqlBaseParser.COLON, 0) + + def NOT(self): + return self.getToken(SqlBaseParser.NOT, 0) + + def NULL(self): + return self.getToken(SqlBaseParser.NULL, 0) + + def commentSpec(self): + return self.getTypedRuleContext(SqlBaseParser.CommentSpecContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_complexColType + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterComplexColType" ): + listener.enterComplexColType(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitComplexColType" ): + listener.exitComplexColType(self) + + + + + def complexColType(self): + + localctx = SqlBaseParser.ComplexColTypeContext(self, self._ctx, self.state) + self.enterRule(localctx, 302, self.RULE_complexColType) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 3324 + self.identifier() + self.state = 3326 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==326: + self.state = 3325 + self.match(SqlBaseParser.COLON) + + + self.state = 3328 + self.dataType() + self.state = 3331 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==172: + self.state = 3329 + self.match(SqlBaseParser.NOT) + self.state = 3330 + self.match(SqlBaseParser.NULL) + + + self.state = 3334 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==45: + self.state = 3333 + self.commentSpec() + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class WhenClauseContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self.condition = None # ExpressionContext + self.result = None # ExpressionContext + + def WHEN(self): + return self.getToken(SqlBaseParser.WHEN, 0) + + def THEN(self): + return self.getToken(SqlBaseParser.THEN, 0) + + def expression(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.ExpressionContext) + else: + return self.getTypedRuleContext(SqlBaseParser.ExpressionContext,i) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_whenClause + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterWhenClause" ): + listener.enterWhenClause(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitWhenClause" ): + listener.exitWhenClause(self) + + + + + def whenClause(self): + + localctx = SqlBaseParser.WhenClauseContext(self, self._ctx, self.state) + self.enterRule(localctx, 304, self.RULE_whenClause) + try: + self.enterOuterAlt(localctx, 1) + self.state = 3336 + self.match(SqlBaseParser.WHEN) + self.state = 3337 + localctx.condition = self.expression() + self.state = 3338 + self.match(SqlBaseParser.THEN) + self.state = 3339 + localctx.result = self.expression() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class WindowClauseContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def WINDOW(self): + return self.getToken(SqlBaseParser.WINDOW, 0) + + def namedWindow(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.NamedWindowContext) + else: + return self.getTypedRuleContext(SqlBaseParser.NamedWindowContext,i) + + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def getRuleIndex(self): + return SqlBaseParser.RULE_windowClause + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterWindowClause" ): + listener.enterWindowClause(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitWindowClause" ): + listener.exitWindowClause(self) + + + + + def windowClause(self): + + localctx = SqlBaseParser.WindowClauseContext(self, self._ctx, self.state) + self.enterRule(localctx, 306, self.RULE_windowClause) + try: + self.enterOuterAlt(localctx, 1) + self.state = 3341 + self.match(SqlBaseParser.WINDOW) + self.state = 3342 + self.namedWindow() + self.state = 3347 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,428,self._ctx) + while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt==1: + self.state = 3343 + self.match(SqlBaseParser.COMMA) + self.state = 3344 + self.namedWindow() + self.state = 3349 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,428,self._ctx) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class NamedWindowContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self.name = None # ErrorCapturingIdentifierContext + + def AS(self): + return self.getToken(SqlBaseParser.AS, 0) + + def windowSpec(self): + return self.getTypedRuleContext(SqlBaseParser.WindowSpecContext,0) + + + def errorCapturingIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.ErrorCapturingIdentifierContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_namedWindow + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterNamedWindow" ): + listener.enterNamedWindow(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitNamedWindow" ): + listener.exitNamedWindow(self) + + + + + def namedWindow(self): + + localctx = SqlBaseParser.NamedWindowContext(self, self._ctx, self.state) + self.enterRule(localctx, 308, self.RULE_namedWindow) + try: + self.enterOuterAlt(localctx, 1) + self.state = 3350 + localctx.name = self.errorCapturingIdentifier() + self.state = 3351 + self.match(SqlBaseParser.AS) + self.state = 3352 + self.windowSpec() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class WindowSpecContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + + def getRuleIndex(self): + return SqlBaseParser.RULE_windowSpec + + + def copyFrom(self, ctx:ParserRuleContext): + super().copyFrom(ctx) + + + + class WindowRefContext(WindowSpecContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.WindowSpecContext + super().__init__(parser) + self.name = None # ErrorCapturingIdentifierContext + self.copyFrom(ctx) + + def errorCapturingIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.ErrorCapturingIdentifierContext,0) + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterWindowRef" ): + listener.enterWindowRef(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitWindowRef" ): + listener.exitWindowRef(self) + + + class WindowDefContext(WindowSpecContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.WindowSpecContext + super().__init__(parser) + self._expression = None # ExpressionContext + self.partition = list() # of ExpressionContexts + self.copyFrom(ctx) + + def LEFT_PAREN(self): + return self.getToken(SqlBaseParser.LEFT_PAREN, 0) + def RIGHT_PAREN(self): + return self.getToken(SqlBaseParser.RIGHT_PAREN, 0) + def CLUSTER(self): + return self.getToken(SqlBaseParser.CLUSTER, 0) + def BY(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.BY) + else: + return self.getToken(SqlBaseParser.BY, i) + def expression(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.ExpressionContext) + else: + return self.getTypedRuleContext(SqlBaseParser.ExpressionContext,i) + + def windowFrame(self): + return self.getTypedRuleContext(SqlBaseParser.WindowFrameContext,0) + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + def sortItem(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.SortItemContext) + else: + return self.getTypedRuleContext(SqlBaseParser.SortItemContext,i) + + def PARTITION(self): + return self.getToken(SqlBaseParser.PARTITION, 0) + def DISTRIBUTE(self): + return self.getToken(SqlBaseParser.DISTRIBUTE, 0) + def ORDER(self): + return self.getToken(SqlBaseParser.ORDER, 0) + def SORT(self): + return self.getToken(SqlBaseParser.SORT, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterWindowDef" ): + listener.enterWindowDef(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitWindowDef" ): + listener.exitWindowDef(self) + + + + def windowSpec(self): + + localctx = SqlBaseParser.WindowSpecContext(self, self._ctx, self.state) + self.enterRule(localctx, 310, self.RULE_windowSpec) + self._la = 0 # Token type + try: + self.state = 3400 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,436,self._ctx) + if la_ == 1: + localctx = SqlBaseParser.WindowRefContext(self, localctx) + self.enterOuterAlt(localctx, 1) + self.state = 3354 + localctx.name = self.errorCapturingIdentifier() + pass + + elif la_ == 2: + localctx = SqlBaseParser.WindowRefContext(self, localctx) + self.enterOuterAlt(localctx, 2) + self.state = 3355 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 3356 + localctx.name = self.errorCapturingIdentifier() + self.state = 3357 + self.match(SqlBaseParser.RIGHT_PAREN) + pass + + elif la_ == 3: + localctx = SqlBaseParser.WindowDefContext(self, localctx) + self.enterOuterAlt(localctx, 3) + self.state = 3359 + self.match(SqlBaseParser.LEFT_PAREN) + self.state = 3394 + self._errHandler.sync(self) + token = self._input.LA(1) + if token in [38]: + self.state = 3360 + self.match(SqlBaseParser.CLUSTER) + self.state = 3361 + self.match(SqlBaseParser.BY) + self.state = 3362 + localctx._expression = self.expression() + localctx.partition.append(localctx._expression) + self.state = 3367 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 3363 + self.match(SqlBaseParser.COMMA) + self.state = 3364 + localctx._expression = self.expression() + localctx.partition.append(localctx._expression) + self.state = 3369 + self._errHandler.sync(self) + _la = self._input.LA(1) + + pass + elif token in [3, 80, 182, 190, 206, 228, 245]: + self.state = 3380 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==80 or _la==190: + self.state = 3370 + _la = self._input.LA(1) + if not(_la==80 or _la==190): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 3371 + self.match(SqlBaseParser.BY) + self.state = 3372 + localctx._expression = self.expression() + localctx.partition.append(localctx._expression) + self.state = 3377 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 3373 + self.match(SqlBaseParser.COMMA) + self.state = 3374 + localctx._expression = self.expression() + localctx.partition.append(localctx._expression) + self.state = 3379 + self._errHandler.sync(self) + _la = self._input.LA(1) + + + + self.state = 3392 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==182 or _la==245: + self.state = 3382 + _la = self._input.LA(1) + if not(_la==182 or _la==245): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 3383 + self.match(SqlBaseParser.BY) + self.state = 3384 + self.sortItem() + self.state = 3389 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 3385 + self.match(SqlBaseParser.COMMA) + self.state = 3386 + self.sortItem() + self.state = 3391 + self._errHandler.sync(self) + _la = self._input.LA(1) + + + + pass + else: + raise NoViableAltException(self) + + self.state = 3397 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==206 or _la==228: + self.state = 3396 + self.windowFrame() + + + self.state = 3399 + self.match(SqlBaseParser.RIGHT_PAREN) + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class WindowFrameContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self.frameType = None # Token + self.start = None # FrameBoundContext + self.end = None # FrameBoundContext + + def RANGE(self): + return self.getToken(SqlBaseParser.RANGE, 0) + + def frameBound(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.FrameBoundContext) + else: + return self.getTypedRuleContext(SqlBaseParser.FrameBoundContext,i) + + + def ROWS(self): + return self.getToken(SqlBaseParser.ROWS, 0) + + def BETWEEN(self): + return self.getToken(SqlBaseParser.BETWEEN, 0) + + def AND(self): + return self.getToken(SqlBaseParser.AND, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_windowFrame + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterWindowFrame" ): + listener.enterWindowFrame(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitWindowFrame" ): + listener.exitWindowFrame(self) + + + + + def windowFrame(self): + + localctx = SqlBaseParser.WindowFrameContext(self, self._ctx, self.state) + self.enterRule(localctx, 312, self.RULE_windowFrame) + try: + self.state = 3418 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,437,self._ctx) + if la_ == 1: + self.enterOuterAlt(localctx, 1) + self.state = 3402 + localctx.frameType = self.match(SqlBaseParser.RANGE) + self.state = 3403 + localctx.start = self.frameBound() + pass + + elif la_ == 2: + self.enterOuterAlt(localctx, 2) + self.state = 3404 + localctx.frameType = self.match(SqlBaseParser.ROWS) + self.state = 3405 + localctx.start = self.frameBound() + pass + + elif la_ == 3: + self.enterOuterAlt(localctx, 3) + self.state = 3406 + localctx.frameType = self.match(SqlBaseParser.RANGE) + self.state = 3407 + self.match(SqlBaseParser.BETWEEN) + self.state = 3408 + localctx.start = self.frameBound() + self.state = 3409 + self.match(SqlBaseParser.AND) + self.state = 3410 + localctx.end = self.frameBound() + pass + + elif la_ == 4: + self.enterOuterAlt(localctx, 4) + self.state = 3412 + localctx.frameType = self.match(SqlBaseParser.ROWS) + self.state = 3413 + self.match(SqlBaseParser.BETWEEN) + self.state = 3414 + localctx.start = self.frameBound() + self.state = 3415 + self.match(SqlBaseParser.AND) + self.state = 3416 + localctx.end = self.frameBound() + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class FrameBoundContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self.boundType = None # Token + + def UNBOUNDED(self): + return self.getToken(SqlBaseParser.UNBOUNDED, 0) + + def PRECEDING(self): + return self.getToken(SqlBaseParser.PRECEDING, 0) + + def FOLLOWING(self): + return self.getToken(SqlBaseParser.FOLLOWING, 0) + + def ROW(self): + return self.getToken(SqlBaseParser.ROW, 0) + + def CURRENT(self): + return self.getToken(SqlBaseParser.CURRENT, 0) + + def expression(self): + return self.getTypedRuleContext(SqlBaseParser.ExpressionContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_frameBound + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterFrameBound" ): + listener.enterFrameBound(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitFrameBound" ): + listener.exitFrameBound(self) + + + + + def frameBound(self): + + localctx = SqlBaseParser.FrameBoundContext(self, self._ctx, self.state) + self.enterRule(localctx, 314, self.RULE_frameBound) + self._la = 0 # Token type + try: + self.state = 3427 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,438,self._ctx) + if la_ == 1: + self.enterOuterAlt(localctx, 1) + self.state = 3420 + self.match(SqlBaseParser.UNBOUNDED) + self.state = 3421 + localctx.boundType = self._input.LT(1) + _la = self._input.LA(1) + if not(_la==102 or _la==199): + localctx.boundType = self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + pass + + elif la_ == 2: + self.enterOuterAlt(localctx, 2) + self.state = 3422 + localctx.boundType = self.match(SqlBaseParser.CURRENT) + self.state = 3423 + self.match(SqlBaseParser.ROW) + pass + + elif la_ == 3: + self.enterOuterAlt(localctx, 3) + self.state = 3424 + self.expression() + self.state = 3425 + localctx.boundType = self._input.LT(1) + _la = self._input.LA(1) + if not(_la==102 or _la==199): + localctx.boundType = self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class QualifiedNameListContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def qualifiedName(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.QualifiedNameContext) + else: + return self.getTypedRuleContext(SqlBaseParser.QualifiedNameContext,i) + + + def COMMA(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.COMMA) + else: + return self.getToken(SqlBaseParser.COMMA, i) + + def getRuleIndex(self): + return SqlBaseParser.RULE_qualifiedNameList + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterQualifiedNameList" ): + listener.enterQualifiedNameList(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitQualifiedNameList" ): + listener.exitQualifiedNameList(self) + + + + + def qualifiedNameList(self): + + localctx = SqlBaseParser.QualifiedNameListContext(self, self._ctx, self.state) + self.enterRule(localctx, 316, self.RULE_qualifiedNameList) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 3429 + self.qualifiedName() + self.state = 3434 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==4: + self.state = 3430 + self.match(SqlBaseParser.COMMA) + self.state = 3431 + self.qualifiedName() + self.state = 3436 + self._errHandler.sync(self) + _la = self._input.LA(1) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class FunctionNameContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def qualifiedName(self): + return self.getTypedRuleContext(SqlBaseParser.QualifiedNameContext,0) + + + def FILTER(self): + return self.getToken(SqlBaseParser.FILTER, 0) + + def LEFT(self): + return self.getToken(SqlBaseParser.LEFT, 0) + + def RIGHT(self): + return self.getToken(SqlBaseParser.RIGHT, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_functionName + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterFunctionName" ): + listener.enterFunctionName(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitFunctionName" ): + listener.exitFunctionName(self) + + + + + def functionName(self): + + localctx = SqlBaseParser.FunctionNameContext(self, self._ctx, self.state) + self.enterRule(localctx, 318, self.RULE_functionName) + try: + self.state = 3441 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,440,self._ctx) + if la_ == 1: + self.enterOuterAlt(localctx, 1) + self.state = 3437 + self.qualifiedName() + pass + + elif la_ == 2: + self.enterOuterAlt(localctx, 2) + self.state = 3438 + self.match(SqlBaseParser.FILTER) + pass + + elif la_ == 3: + self.enterOuterAlt(localctx, 3) + self.state = 3439 + self.match(SqlBaseParser.LEFT) + pass + + elif la_ == 4: + self.enterOuterAlt(localctx, 4) + self.state = 3440 + self.match(SqlBaseParser.RIGHT) + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class QualifiedNameContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def identifier(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.IdentifierContext) + else: + return self.getTypedRuleContext(SqlBaseParser.IdentifierContext,i) + + + def DOT(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.DOT) + else: + return self.getToken(SqlBaseParser.DOT, i) + + def getRuleIndex(self): + return SqlBaseParser.RULE_qualifiedName + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterQualifiedName" ): + listener.enterQualifiedName(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitQualifiedName" ): + listener.exitQualifiedName(self) + + + + + def qualifiedName(self): + + localctx = SqlBaseParser.QualifiedNameContext(self, self._ctx, self.state) + self.enterRule(localctx, 320, self.RULE_qualifiedName) + try: + self.enterOuterAlt(localctx, 1) + self.state = 3443 + self.identifier() + self.state = 3448 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,441,self._ctx) + while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt==1: + self.state = 3444 + self.match(SqlBaseParser.DOT) + self.state = 3445 + self.identifier() + self.state = 3450 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,441,self._ctx) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class ErrorCapturingIdentifierContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def identifier(self): + return self.getTypedRuleContext(SqlBaseParser.IdentifierContext,0) + + + def errorCapturingIdentifierExtra(self): + return self.getTypedRuleContext(SqlBaseParser.ErrorCapturingIdentifierExtraContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_errorCapturingIdentifier + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterErrorCapturingIdentifier" ): + listener.enterErrorCapturingIdentifier(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitErrorCapturingIdentifier" ): + listener.exitErrorCapturingIdentifier(self) + + + + + def errorCapturingIdentifier(self): + + localctx = SqlBaseParser.ErrorCapturingIdentifierContext(self, self._ctx, self.state) + self.enterRule(localctx, 322, self.RULE_errorCapturingIdentifier) + try: + self.enterOuterAlt(localctx, 1) + self.state = 3451 + self.identifier() + self.state = 3452 + self.errorCapturingIdentifierExtra() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class ErrorCapturingIdentifierExtraContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + + def getRuleIndex(self): + return SqlBaseParser.RULE_errorCapturingIdentifierExtra + + + def copyFrom(self, ctx:ParserRuleContext): + super().copyFrom(ctx) + + + + class ErrorIdentContext(ErrorCapturingIdentifierExtraContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.ErrorCapturingIdentifierExtraContext + super().__init__(parser) + self.copyFrom(ctx) + + def MINUS(self, i:int=None): + if i is None: + return self.getTokens(SqlBaseParser.MINUS) + else: + return self.getToken(SqlBaseParser.MINUS, i) + def identifier(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(SqlBaseParser.IdentifierContext) + else: + return self.getTypedRuleContext(SqlBaseParser.IdentifierContext,i) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterErrorIdent" ): + listener.enterErrorIdent(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitErrorIdent" ): + listener.exitErrorIdent(self) + + + class RealIdentContext(ErrorCapturingIdentifierExtraContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.ErrorCapturingIdentifierExtraContext + super().__init__(parser) + self.copyFrom(ctx) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterRealIdent" ): + listener.enterRealIdent(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitRealIdent" ): + listener.exitRealIdent(self) + + + + def errorCapturingIdentifierExtra(self): + + localctx = SqlBaseParser.ErrorCapturingIdentifierExtraContext(self, self._ctx, self.state) + self.enterRule(localctx, 324, self.RULE_errorCapturingIdentifierExtra) + try: + self.state = 3461 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,443,self._ctx) + if la_ == 1: + localctx = SqlBaseParser.ErrorIdentContext(self, localctx) + self.enterOuterAlt(localctx, 1) + self.state = 3456 + self._errHandler.sync(self) + _alt = 1 + while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt == 1: + self.state = 3454 + self.match(SqlBaseParser.MINUS) + self.state = 3455 + self.identifier() + + else: + raise NoViableAltException(self) + self.state = 3458 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,442,self._ctx) + + pass + + elif la_ == 2: + localctx = SqlBaseParser.RealIdentContext(self, localctx) + self.enterOuterAlt(localctx, 2) + + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class IdentifierContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def strictIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.StrictIdentifierContext,0) + + + def strictNonReserved(self): + return self.getTypedRuleContext(SqlBaseParser.StrictNonReservedContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_identifier + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterIdentifier" ): + listener.enterIdentifier(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitIdentifier" ): + listener.exitIdentifier(self) + + + + + def identifier(self): + + localctx = SqlBaseParser.IdentifierContext(self, self._ctx, self.state) + self.enterRule(localctx, 326, self.RULE_identifier) + try: + self.state = 3465 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,444,self._ctx) + if la_ == 1: + self.enterOuterAlt(localctx, 1) + self.state = 3463 + self.strictIdentifier() + pass + + elif la_ == 2: + self.enterOuterAlt(localctx, 2) + self.state = 3464 + self.strictNonReserved() + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class StrictIdentifierContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + + def getRuleIndex(self): + return SqlBaseParser.RULE_strictIdentifier + + + def copyFrom(self, ctx:ParserRuleContext): + super().copyFrom(ctx) + + + + class QuotedIdentifierAlternativeContext(StrictIdentifierContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StrictIdentifierContext + super().__init__(parser) + self.copyFrom(ctx) + + def quotedIdentifier(self): + return self.getTypedRuleContext(SqlBaseParser.QuotedIdentifierContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterQuotedIdentifierAlternative" ): + listener.enterQuotedIdentifierAlternative(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitQuotedIdentifierAlternative" ): + listener.exitQuotedIdentifierAlternative(self) + + + class UnquotedIdentifierContext(StrictIdentifierContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.StrictIdentifierContext + super().__init__(parser) + self.copyFrom(ctx) + + def IDENTIFIER(self): + return self.getToken(SqlBaseParser.IDENTIFIER, 0) + def ansiNonReserved(self): + return self.getTypedRuleContext(SqlBaseParser.AnsiNonReservedContext,0) + + def nonReserved(self): + return self.getTypedRuleContext(SqlBaseParser.NonReservedContext,0) + + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterUnquotedIdentifier" ): + listener.enterUnquotedIdentifier(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitUnquotedIdentifier" ): + listener.exitUnquotedIdentifier(self) + + + + def strictIdentifier(self): + + localctx = SqlBaseParser.StrictIdentifierContext(self, self._ctx, self.state) + self.enterRule(localctx, 328, self.RULE_strictIdentifier) + try: + self.state = 3471 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,445,self._ctx) + if la_ == 1: + localctx = SqlBaseParser.UnquotedIdentifierContext(self, localctx) + self.enterOuterAlt(localctx, 1) + self.state = 3467 + self.match(SqlBaseParser.IDENTIFIER) + pass + + elif la_ == 2: + localctx = SqlBaseParser.QuotedIdentifierAlternativeContext(self, localctx) + self.enterOuterAlt(localctx, 2) + self.state = 3468 + self.quotedIdentifier() + pass + + elif la_ == 3: + localctx = SqlBaseParser.UnquotedIdentifierContext(self, localctx) + self.enterOuterAlt(localctx, 3) + self.state = 3469 + self.ansiNonReserved() + pass + + elif la_ == 4: + localctx = SqlBaseParser.UnquotedIdentifierContext(self, localctx) + self.enterOuterAlt(localctx, 4) + self.state = 3470 + self.nonReserved() + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class QuotedIdentifierContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def BACKQUOTED_IDENTIFIER(self): + return self.getToken(SqlBaseParser.BACKQUOTED_IDENTIFIER, 0) + + def DOUBLEQUOTED_STRING(self): + return self.getToken(SqlBaseParser.DOUBLEQUOTED_STRING, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_quotedIdentifier + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterQuotedIdentifier" ): + listener.enterQuotedIdentifier(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitQuotedIdentifier" ): + listener.exitQuotedIdentifier(self) + + + + + def quotedIdentifier(self): + + localctx = SqlBaseParser.QuotedIdentifierContext(self, self._ctx, self.state) + self.enterRule(localctx, 330, self.RULE_quotedIdentifier) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 3473 + _la = self._input.LA(1) + if not(_la==331 or _la==342): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class BackQuotedIdentifierContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def BACKQUOTED_IDENTIFIER(self): + return self.getToken(SqlBaseParser.BACKQUOTED_IDENTIFIER, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_backQuotedIdentifier + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterBackQuotedIdentifier" ): + listener.enterBackQuotedIdentifier(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitBackQuotedIdentifier" ): + listener.exitBackQuotedIdentifier(self) + + + + + def backQuotedIdentifier(self): + + localctx = SqlBaseParser.BackQuotedIdentifierContext(self, self._ctx, self.state) + self.enterRule(localctx, 332, self.RULE_backQuotedIdentifier) + try: + self.enterOuterAlt(localctx, 1) + self.state = 3475 + self.match(SqlBaseParser.BACKQUOTED_IDENTIFIER) + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class NumberContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + + def getRuleIndex(self): + return SqlBaseParser.RULE_number + + + def copyFrom(self, ctx:ParserRuleContext): + super().copyFrom(ctx) + + + + class DecimalLiteralContext(NumberContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.NumberContext + super().__init__(parser) + self.copyFrom(ctx) + + def DECIMAL_VALUE(self): + return self.getToken(SqlBaseParser.DECIMAL_VALUE, 0) + def MINUS(self): + return self.getToken(SqlBaseParser.MINUS, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterDecimalLiteral" ): + listener.enterDecimalLiteral(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitDecimalLiteral" ): + listener.exitDecimalLiteral(self) + + + class BigIntLiteralContext(NumberContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.NumberContext + super().__init__(parser) + self.copyFrom(ctx) + + def BIGINT_LITERAL(self): + return self.getToken(SqlBaseParser.BIGINT_LITERAL, 0) + def MINUS(self): + return self.getToken(SqlBaseParser.MINUS, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterBigIntLiteral" ): + listener.enterBigIntLiteral(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitBigIntLiteral" ): + listener.exitBigIntLiteral(self) + + + class TinyIntLiteralContext(NumberContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.NumberContext + super().__init__(parser) + self.copyFrom(ctx) + + def TINYINT_LITERAL(self): + return self.getToken(SqlBaseParser.TINYINT_LITERAL, 0) + def MINUS(self): + return self.getToken(SqlBaseParser.MINUS, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterTinyIntLiteral" ): + listener.enterTinyIntLiteral(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitTinyIntLiteral" ): + listener.exitTinyIntLiteral(self) + + + class LegacyDecimalLiteralContext(NumberContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.NumberContext + super().__init__(parser) + self.copyFrom(ctx) + + def EXPONENT_VALUE(self): + return self.getToken(SqlBaseParser.EXPONENT_VALUE, 0) + def DECIMAL_VALUE(self): + return self.getToken(SqlBaseParser.DECIMAL_VALUE, 0) + def MINUS(self): + return self.getToken(SqlBaseParser.MINUS, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterLegacyDecimalLiteral" ): + listener.enterLegacyDecimalLiteral(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitLegacyDecimalLiteral" ): + listener.exitLegacyDecimalLiteral(self) + + + class BigDecimalLiteralContext(NumberContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.NumberContext + super().__init__(parser) + self.copyFrom(ctx) + + def BIGDECIMAL_LITERAL(self): + return self.getToken(SqlBaseParser.BIGDECIMAL_LITERAL, 0) + def MINUS(self): + return self.getToken(SqlBaseParser.MINUS, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterBigDecimalLiteral" ): + listener.enterBigDecimalLiteral(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitBigDecimalLiteral" ): + listener.exitBigDecimalLiteral(self) + + + class ExponentLiteralContext(NumberContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.NumberContext + super().__init__(parser) + self.copyFrom(ctx) + + def EXPONENT_VALUE(self): + return self.getToken(SqlBaseParser.EXPONENT_VALUE, 0) + def MINUS(self): + return self.getToken(SqlBaseParser.MINUS, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterExponentLiteral" ): + listener.enterExponentLiteral(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitExponentLiteral" ): + listener.exitExponentLiteral(self) + + + class DoubleLiteralContext(NumberContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.NumberContext + super().__init__(parser) + self.copyFrom(ctx) + + def DOUBLE_LITERAL(self): + return self.getToken(SqlBaseParser.DOUBLE_LITERAL, 0) + def MINUS(self): + return self.getToken(SqlBaseParser.MINUS, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterDoubleLiteral" ): + listener.enterDoubleLiteral(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitDoubleLiteral" ): + listener.exitDoubleLiteral(self) + + + class IntegerLiteralContext(NumberContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.NumberContext + super().__init__(parser) + self.copyFrom(ctx) + + def INTEGER_VALUE(self): + return self.getToken(SqlBaseParser.INTEGER_VALUE, 0) + def MINUS(self): + return self.getToken(SqlBaseParser.MINUS, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterIntegerLiteral" ): + listener.enterIntegerLiteral(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitIntegerLiteral" ): + listener.exitIntegerLiteral(self) + + + class FloatLiteralContext(NumberContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.NumberContext + super().__init__(parser) + self.copyFrom(ctx) + + def FLOAT_LITERAL(self): + return self.getToken(SqlBaseParser.FLOAT_LITERAL, 0) + def MINUS(self): + return self.getToken(SqlBaseParser.MINUS, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterFloatLiteral" ): + listener.enterFloatLiteral(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitFloatLiteral" ): + listener.exitFloatLiteral(self) + + + class SmallIntLiteralContext(NumberContext): + + def __init__(self, parser, ctx:ParserRuleContext): # actually a SqlBaseParser.NumberContext + super().__init__(parser) + self.copyFrom(ctx) + + def SMALLINT_LITERAL(self): + return self.getToken(SqlBaseParser.SMALLINT_LITERAL, 0) + def MINUS(self): + return self.getToken(SqlBaseParser.MINUS, 0) + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterSmallIntLiteral" ): + listener.enterSmallIntLiteral(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitSmallIntLiteral" ): + listener.exitSmallIntLiteral(self) + + + + def number(self): + + localctx = SqlBaseParser.NumberContext(self, self._ctx, self.state) + self.enterRule(localctx, 334, self.RULE_number) + self._la = 0 # Token type + try: + self.state = 3517 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,456,self._ctx) + if la_ == 1: + localctx = SqlBaseParser.ExponentLiteralContext(self, localctx) + self.enterOuterAlt(localctx, 1) + self.state = 3478 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==317: + self.state = 3477 + self.match(SqlBaseParser.MINUS) + + + self.state = 3480 + self.match(SqlBaseParser.EXPONENT_VALUE) + pass + + elif la_ == 2: + localctx = SqlBaseParser.DecimalLiteralContext(self, localctx) + self.enterOuterAlt(localctx, 2) + self.state = 3482 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==317: + self.state = 3481 + self.match(SqlBaseParser.MINUS) + + + self.state = 3484 + self.match(SqlBaseParser.DECIMAL_VALUE) + pass + + elif la_ == 3: + localctx = SqlBaseParser.LegacyDecimalLiteralContext(self, localctx) + self.enterOuterAlt(localctx, 3) + self.state = 3486 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==317: + self.state = 3485 + self.match(SqlBaseParser.MINUS) + + + self.state = 3488 + _la = self._input.LA(1) + if not(_la==336 or _la==337): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + pass + + elif la_ == 4: + localctx = SqlBaseParser.IntegerLiteralContext(self, localctx) + self.enterOuterAlt(localctx, 4) + self.state = 3490 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==317: + self.state = 3489 + self.match(SqlBaseParser.MINUS) + + + self.state = 3492 + self.match(SqlBaseParser.INTEGER_VALUE) + pass + + elif la_ == 5: + localctx = SqlBaseParser.BigIntLiteralContext(self, localctx) + self.enterOuterAlt(localctx, 5) + self.state = 3494 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==317: + self.state = 3493 + self.match(SqlBaseParser.MINUS) + + + self.state = 3496 + self.match(SqlBaseParser.BIGINT_LITERAL) + pass + + elif la_ == 6: + localctx = SqlBaseParser.SmallIntLiteralContext(self, localctx) + self.enterOuterAlt(localctx, 6) + self.state = 3498 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==317: + self.state = 3497 + self.match(SqlBaseParser.MINUS) + + + self.state = 3500 + self.match(SqlBaseParser.SMALLINT_LITERAL) + pass + + elif la_ == 7: + localctx = SqlBaseParser.TinyIntLiteralContext(self, localctx) + self.enterOuterAlt(localctx, 7) + self.state = 3502 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==317: + self.state = 3501 + self.match(SqlBaseParser.MINUS) + + + self.state = 3504 + self.match(SqlBaseParser.TINYINT_LITERAL) + pass + + elif la_ == 8: + localctx = SqlBaseParser.DoubleLiteralContext(self, localctx) + self.enterOuterAlt(localctx, 8) + self.state = 3506 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==317: + self.state = 3505 + self.match(SqlBaseParser.MINUS) + + + self.state = 3508 + self.match(SqlBaseParser.DOUBLE_LITERAL) + pass + + elif la_ == 9: + localctx = SqlBaseParser.FloatLiteralContext(self, localctx) + self.enterOuterAlt(localctx, 9) + self.state = 3510 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==317: + self.state = 3509 + self.match(SqlBaseParser.MINUS) + + + self.state = 3512 + self.match(SqlBaseParser.FLOAT_LITERAL) + pass + + elif la_ == 10: + localctx = SqlBaseParser.BigDecimalLiteralContext(self, localctx) + self.enterOuterAlt(localctx, 10) + self.state = 3514 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==317: + self.state = 3513 + self.match(SqlBaseParser.MINUS) + + + self.state = 3516 + self.match(SqlBaseParser.BIGDECIMAL_LITERAL) + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class AlterColumnActionContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + self.setOrDrop = None # Token + self.dropDefault = None # Token + + def TYPE(self): + return self.getToken(SqlBaseParser.TYPE, 0) + + def dataType(self): + return self.getTypedRuleContext(SqlBaseParser.DataTypeContext,0) + + + def commentSpec(self): + return self.getTypedRuleContext(SqlBaseParser.CommentSpecContext,0) + + + def colPosition(self): + return self.getTypedRuleContext(SqlBaseParser.ColPositionContext,0) + + + def NOT(self): + return self.getToken(SqlBaseParser.NOT, 0) + + def NULL(self): + return self.getToken(SqlBaseParser.NULL, 0) + + def SET(self): + return self.getToken(SqlBaseParser.SET, 0) + + def DROP(self): + return self.getToken(SqlBaseParser.DROP, 0) + + def defaultExpression(self): + return self.getTypedRuleContext(SqlBaseParser.DefaultExpressionContext,0) + + + def DEFAULT(self): + return self.getToken(SqlBaseParser.DEFAULT, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_alterColumnAction + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterAlterColumnAction" ): + listener.enterAlterColumnAction(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitAlterColumnAction" ): + listener.exitAlterColumnAction(self) + + + + + def alterColumnAction(self): + + localctx = SqlBaseParser.AlterColumnActionContext(self, self._ctx, self.state) + self.enterRule(localctx, 336, self.RULE_alterColumnAction) + self._la = 0 # Token type + try: + self.state = 3530 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,457,self._ctx) + if la_ == 1: + self.enterOuterAlt(localctx, 1) + self.state = 3519 + self.match(SqlBaseParser.TYPE) + self.state = 3520 + self.dataType() + pass + + elif la_ == 2: + self.enterOuterAlt(localctx, 2) + self.state = 3521 + self.commentSpec() + pass + + elif la_ == 3: + self.enterOuterAlt(localctx, 3) + self.state = 3522 + self.colPosition() + pass + + elif la_ == 4: + self.enterOuterAlt(localctx, 4) + self.state = 3523 + localctx.setOrDrop = self._input.LT(1) + _la = self._input.LA(1) + if not(_la==82 or _la==239): + localctx.setOrDrop = self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 3524 + self.match(SqlBaseParser.NOT) + self.state = 3525 + self.match(SqlBaseParser.NULL) + pass + + elif la_ == 5: + self.enterOuterAlt(localctx, 5) + self.state = 3526 + self.match(SqlBaseParser.SET) + self.state = 3527 + self.defaultExpression() + pass + + elif la_ == 6: + self.enterOuterAlt(localctx, 6) + self.state = 3528 + localctx.dropDefault = self.match(SqlBaseParser.DROP) + self.state = 3529 + self.match(SqlBaseParser.DEFAULT) + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class StringLitContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def STRING(self): + return self.getToken(SqlBaseParser.STRING, 0) + + def DOUBLEQUOTED_STRING(self): + return self.getToken(SqlBaseParser.DOUBLEQUOTED_STRING, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_stringLit + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterStringLit" ): + listener.enterStringLit(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitStringLit" ): + listener.exitStringLit(self) + + + + + def stringLit(self): + + localctx = SqlBaseParser.StringLitContext(self, self._ctx, self.state) + self.enterRule(localctx, 338, self.RULE_stringLit) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 3532 + _la = self._input.LA(1) + if not(_la==330 or _la==331): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class CommentContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def stringLit(self): + return self.getTypedRuleContext(SqlBaseParser.StringLitContext,0) + + + def NULL(self): + return self.getToken(SqlBaseParser.NULL, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_comment + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterComment" ): + listener.enterComment(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitComment" ): + listener.exitComment(self) + + + + + def comment(self): + + localctx = SqlBaseParser.CommentContext(self, self._ctx, self.state) + self.enterRule(localctx, 340, self.RULE_comment) + try: + self.state = 3536 + self._errHandler.sync(self) + token = self._input.LA(1) + if token in [330, 331]: + self.enterOuterAlt(localctx, 1) + self.state = 3534 + self.stringLit() + pass + elif token in [173]: + self.enterOuterAlt(localctx, 2) + self.state = 3535 + self.match(SqlBaseParser.NULL) + pass + else: + raise NoViableAltException(self) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class VersionContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def INTEGER_VALUE(self): + return self.getToken(SqlBaseParser.INTEGER_VALUE, 0) + + def stringLit(self): + return self.getTypedRuleContext(SqlBaseParser.StringLitContext,0) + + + def getRuleIndex(self): + return SqlBaseParser.RULE_version + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterVersion" ): + listener.enterVersion(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitVersion" ): + listener.exitVersion(self) + + + + + def version(self): + + localctx = SqlBaseParser.VersionContext(self, self._ctx, self.state) + self.enterRule(localctx, 342, self.RULE_version) + try: + self.state = 3540 + self._errHandler.sync(self) + token = self._input.LA(1) + if token in [335]: + self.enterOuterAlt(localctx, 1) + self.state = 3538 + self.match(SqlBaseParser.INTEGER_VALUE) + pass + elif token in [330, 331]: + self.enterOuterAlt(localctx, 2) + self.state = 3539 + self.stringLit() + pass + else: + raise NoViableAltException(self) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class AnsiNonReservedContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def ADD(self): + return self.getToken(SqlBaseParser.ADD, 0) + + def AFTER(self): + return self.getToken(SqlBaseParser.AFTER, 0) + + def ALTER(self): + return self.getToken(SqlBaseParser.ALTER, 0) + + def ALWAYS(self): + return self.getToken(SqlBaseParser.ALWAYS, 0) + + def ANALYZE(self): + return self.getToken(SqlBaseParser.ANALYZE, 0) + + def ANTI(self): + return self.getToken(SqlBaseParser.ANTI, 0) + + def ANY_VALUE(self): + return self.getToken(SqlBaseParser.ANY_VALUE, 0) + + def ARCHIVE(self): + return self.getToken(SqlBaseParser.ARCHIVE, 0) + + def ARRAY(self): + return self.getToken(SqlBaseParser.ARRAY, 0) + + def ASC(self): + return self.getToken(SqlBaseParser.ASC, 0) + + def AT(self): + return self.getToken(SqlBaseParser.AT, 0) + + def BETWEEN(self): + return self.getToken(SqlBaseParser.BETWEEN, 0) + + def BUCKET(self): + return self.getToken(SqlBaseParser.BUCKET, 0) + + def BUCKETS(self): + return self.getToken(SqlBaseParser.BUCKETS, 0) + + def BY(self): + return self.getToken(SqlBaseParser.BY, 0) + + def CACHE(self): + return self.getToken(SqlBaseParser.CACHE, 0) + + def CASCADE(self): + return self.getToken(SqlBaseParser.CASCADE, 0) + + def CATALOG(self): + return self.getToken(SqlBaseParser.CATALOG, 0) + + def CATALOGS(self): + return self.getToken(SqlBaseParser.CATALOGS, 0) + + def CHANGE(self): + return self.getToken(SqlBaseParser.CHANGE, 0) + + def CLEAR(self): + return self.getToken(SqlBaseParser.CLEAR, 0) + + def CLUSTER(self): + return self.getToken(SqlBaseParser.CLUSTER, 0) + + def CLUSTERED(self): + return self.getToken(SqlBaseParser.CLUSTERED, 0) + + def CODEGEN(self): + return self.getToken(SqlBaseParser.CODEGEN, 0) + + def COLLECTION(self): + return self.getToken(SqlBaseParser.COLLECTION, 0) + + def COLUMNS(self): + return self.getToken(SqlBaseParser.COLUMNS, 0) + + def COMMENT(self): + return self.getToken(SqlBaseParser.COMMENT, 0) + + def COMMIT(self): + return self.getToken(SqlBaseParser.COMMIT, 0) + + def COMPACT(self): + return self.getToken(SqlBaseParser.COMPACT, 0) + + def COMPACTIONS(self): + return self.getToken(SqlBaseParser.COMPACTIONS, 0) + + def COMPUTE(self): + return self.getToken(SqlBaseParser.COMPUTE, 0) + + def CONCATENATE(self): + return self.getToken(SqlBaseParser.CONCATENATE, 0) + + def COST(self): + return self.getToken(SqlBaseParser.COST, 0) + + def CUBE(self): + return self.getToken(SqlBaseParser.CUBE, 0) + + def CURRENT(self): + return self.getToken(SqlBaseParser.CURRENT, 0) + + def DATA(self): + return self.getToken(SqlBaseParser.DATA, 0) + + def DATABASE(self): + return self.getToken(SqlBaseParser.DATABASE, 0) + + def DATABASES(self): + return self.getToken(SqlBaseParser.DATABASES, 0) + + def DATEADD(self): + return self.getToken(SqlBaseParser.DATEADD, 0) + + def DATEDIFF(self): + return self.getToken(SqlBaseParser.DATEDIFF, 0) + + def DAY(self): + return self.getToken(SqlBaseParser.DAY, 0) + + def DAYS(self): + return self.getToken(SqlBaseParser.DAYS, 0) + + def DAYOFYEAR(self): + return self.getToken(SqlBaseParser.DAYOFYEAR, 0) + + def DBPROPERTIES(self): + return self.getToken(SqlBaseParser.DBPROPERTIES, 0) + + def DEFAULT(self): + return self.getToken(SqlBaseParser.DEFAULT, 0) + + def DEFINED(self): + return self.getToken(SqlBaseParser.DEFINED, 0) + + def DELETE(self): + return self.getToken(SqlBaseParser.DELETE, 0) + + def DELIMITED(self): + return self.getToken(SqlBaseParser.DELIMITED, 0) + + def DESC(self): + return self.getToken(SqlBaseParser.DESC, 0) + + def DESCRIBE(self): + return self.getToken(SqlBaseParser.DESCRIBE, 0) + + def DFS(self): + return self.getToken(SqlBaseParser.DFS, 0) + + def DIRECTORIES(self): + return self.getToken(SqlBaseParser.DIRECTORIES, 0) + + def DIRECTORY(self): + return self.getToken(SqlBaseParser.DIRECTORY, 0) + + def DISTRIBUTE(self): + return self.getToken(SqlBaseParser.DISTRIBUTE, 0) + + def DIV(self): + return self.getToken(SqlBaseParser.DIV, 0) + + def DROP(self): + return self.getToken(SqlBaseParser.DROP, 0) + + def ESCAPED(self): + return self.getToken(SqlBaseParser.ESCAPED, 0) + + def EXCHANGE(self): + return self.getToken(SqlBaseParser.EXCHANGE, 0) + + def EXCLUDE(self): + return self.getToken(SqlBaseParser.EXCLUDE, 0) + + def EXISTS(self): + return self.getToken(SqlBaseParser.EXISTS, 0) + + def EXPLAIN(self): + return self.getToken(SqlBaseParser.EXPLAIN, 0) + + def EXPORT(self): + return self.getToken(SqlBaseParser.EXPORT, 0) + + def EXTENDED(self): + return self.getToken(SqlBaseParser.EXTENDED, 0) + + def EXTERNAL(self): + return self.getToken(SqlBaseParser.EXTERNAL, 0) + + def EXTRACT(self): + return self.getToken(SqlBaseParser.EXTRACT, 0) + + def FIELDS(self): + return self.getToken(SqlBaseParser.FIELDS, 0) + + def FILEFORMAT(self): + return self.getToken(SqlBaseParser.FILEFORMAT, 0) + + def FIRST(self): + return self.getToken(SqlBaseParser.FIRST, 0) + + def FOLLOWING(self): + return self.getToken(SqlBaseParser.FOLLOWING, 0) + + def FORMAT(self): + return self.getToken(SqlBaseParser.FORMAT, 0) + + def FORMATTED(self): + return self.getToken(SqlBaseParser.FORMATTED, 0) + + def FUNCTION(self): + return self.getToken(SqlBaseParser.FUNCTION, 0) + + def FUNCTIONS(self): + return self.getToken(SqlBaseParser.FUNCTIONS, 0) + + def GENERATED(self): + return self.getToken(SqlBaseParser.GENERATED, 0) + + def GLOBAL(self): + return self.getToken(SqlBaseParser.GLOBAL, 0) + + def GROUPING(self): + return self.getToken(SqlBaseParser.GROUPING, 0) + + def HOUR(self): + return self.getToken(SqlBaseParser.HOUR, 0) + + def HOURS(self): + return self.getToken(SqlBaseParser.HOURS, 0) + + def IF(self): + return self.getToken(SqlBaseParser.IF, 0) + + def IGNORE(self): + return self.getToken(SqlBaseParser.IGNORE, 0) + + def IMPORT(self): + return self.getToken(SqlBaseParser.IMPORT, 0) + + def INCLUDE(self): + return self.getToken(SqlBaseParser.INCLUDE, 0) + + def INDEX(self): + return self.getToken(SqlBaseParser.INDEX, 0) + + def INDEXES(self): + return self.getToken(SqlBaseParser.INDEXES, 0) + + def INPATH(self): + return self.getToken(SqlBaseParser.INPATH, 0) + + def INPUTFORMAT(self): + return self.getToken(SqlBaseParser.INPUTFORMAT, 0) + + def INSERT(self): + return self.getToken(SqlBaseParser.INSERT, 0) + + def INTERVAL(self): + return self.getToken(SqlBaseParser.INTERVAL, 0) + + def ITEMS(self): + return self.getToken(SqlBaseParser.ITEMS, 0) + + def KEYS(self): + return self.getToken(SqlBaseParser.KEYS, 0) + + def LAST(self): + return self.getToken(SqlBaseParser.LAST, 0) + + def LAZY(self): + return self.getToken(SqlBaseParser.LAZY, 0) + + def LIKE(self): + return self.getToken(SqlBaseParser.LIKE, 0) + + def ILIKE(self): + return self.getToken(SqlBaseParser.ILIKE, 0) + + def LIMIT(self): + return self.getToken(SqlBaseParser.LIMIT, 0) + + def LINES(self): + return self.getToken(SqlBaseParser.LINES, 0) + + def LIST(self): + return self.getToken(SqlBaseParser.LIST, 0) + + def LOAD(self): + return self.getToken(SqlBaseParser.LOAD, 0) + + def LOCAL(self): + return self.getToken(SqlBaseParser.LOCAL, 0) + + def LOCATION(self): + return self.getToken(SqlBaseParser.LOCATION, 0) + + def LOCK(self): + return self.getToken(SqlBaseParser.LOCK, 0) + + def LOCKS(self): + return self.getToken(SqlBaseParser.LOCKS, 0) + + def LOGICAL(self): + return self.getToken(SqlBaseParser.LOGICAL, 0) + + def MACRO(self): + return self.getToken(SqlBaseParser.MACRO, 0) + + def MAP(self): + return self.getToken(SqlBaseParser.MAP, 0) + + def MATCHED(self): + return self.getToken(SqlBaseParser.MATCHED, 0) + + def MERGE(self): + return self.getToken(SqlBaseParser.MERGE, 0) + + def MICROSECOND(self): + return self.getToken(SqlBaseParser.MICROSECOND, 0) + + def MICROSECONDS(self): + return self.getToken(SqlBaseParser.MICROSECONDS, 0) + + def MILLISECOND(self): + return self.getToken(SqlBaseParser.MILLISECOND, 0) + + def MILLISECONDS(self): + return self.getToken(SqlBaseParser.MILLISECONDS, 0) + + def MINUTE(self): + return self.getToken(SqlBaseParser.MINUTE, 0) + + def MINUTES(self): + return self.getToken(SqlBaseParser.MINUTES, 0) + + def MONTH(self): + return self.getToken(SqlBaseParser.MONTH, 0) + + def MONTHS(self): + return self.getToken(SqlBaseParser.MONTHS, 0) + + def MSCK(self): + return self.getToken(SqlBaseParser.MSCK, 0) + + def NAMESPACE(self): + return self.getToken(SqlBaseParser.NAMESPACE, 0) + + def NAMESPACES(self): + return self.getToken(SqlBaseParser.NAMESPACES, 0) + + def NANOSECOND(self): + return self.getToken(SqlBaseParser.NANOSECOND, 0) + + def NANOSECONDS(self): + return self.getToken(SqlBaseParser.NANOSECONDS, 0) + + def NO(self): + return self.getToken(SqlBaseParser.NO, 0) + + def NULLS(self): + return self.getToken(SqlBaseParser.NULLS, 0) + + def OF(self): + return self.getToken(SqlBaseParser.OF, 0) + + def OPTION(self): + return self.getToken(SqlBaseParser.OPTION, 0) + + def OPTIONS(self): + return self.getToken(SqlBaseParser.OPTIONS, 0) + + def OUT(self): + return self.getToken(SqlBaseParser.OUT, 0) + + def OUTPUTFORMAT(self): + return self.getToken(SqlBaseParser.OUTPUTFORMAT, 0) + + def OVER(self): + return self.getToken(SqlBaseParser.OVER, 0) + + def OVERLAY(self): + return self.getToken(SqlBaseParser.OVERLAY, 0) + + def OVERWRITE(self): + return self.getToken(SqlBaseParser.OVERWRITE, 0) + + def PARTITION(self): + return self.getToken(SqlBaseParser.PARTITION, 0) + + def PARTITIONED(self): + return self.getToken(SqlBaseParser.PARTITIONED, 0) + + def PARTITIONS(self): + return self.getToken(SqlBaseParser.PARTITIONS, 0) + + def PERCENTLIT(self): + return self.getToken(SqlBaseParser.PERCENTLIT, 0) + + def PIVOT(self): + return self.getToken(SqlBaseParser.PIVOT, 0) + + def PLACING(self): + return self.getToken(SqlBaseParser.PLACING, 0) + + def POSITION(self): + return self.getToken(SqlBaseParser.POSITION, 0) + + def PRECEDING(self): + return self.getToken(SqlBaseParser.PRECEDING, 0) + + def PRINCIPALS(self): + return self.getToken(SqlBaseParser.PRINCIPALS, 0) + + def PROPERTIES(self): + return self.getToken(SqlBaseParser.PROPERTIES, 0) + + def PURGE(self): + return self.getToken(SqlBaseParser.PURGE, 0) + + def QUARTER(self): + return self.getToken(SqlBaseParser.QUARTER, 0) + + def QUERY(self): + return self.getToken(SqlBaseParser.QUERY, 0) + + def RANGE(self): + return self.getToken(SqlBaseParser.RANGE, 0) + + def RECORDREADER(self): + return self.getToken(SqlBaseParser.RECORDREADER, 0) + + def RECORDWRITER(self): + return self.getToken(SqlBaseParser.RECORDWRITER, 0) + + def RECOVER(self): + return self.getToken(SqlBaseParser.RECOVER, 0) + + def REDUCE(self): + return self.getToken(SqlBaseParser.REDUCE, 0) + + def REFRESH(self): + return self.getToken(SqlBaseParser.REFRESH, 0) + + def RENAME(self): + return self.getToken(SqlBaseParser.RENAME, 0) + + def REPAIR(self): + return self.getToken(SqlBaseParser.REPAIR, 0) + + def REPEATABLE(self): + return self.getToken(SqlBaseParser.REPEATABLE, 0) + + def REPLACE(self): + return self.getToken(SqlBaseParser.REPLACE, 0) + + def RESET(self): + return self.getToken(SqlBaseParser.RESET, 0) + + def RESPECT(self): + return self.getToken(SqlBaseParser.RESPECT, 0) + + def RESTRICT(self): + return self.getToken(SqlBaseParser.RESTRICT, 0) + + def REVOKE(self): + return self.getToken(SqlBaseParser.REVOKE, 0) + + def RLIKE(self): + return self.getToken(SqlBaseParser.RLIKE, 0) + + def ROLE(self): + return self.getToken(SqlBaseParser.ROLE, 0) + + def ROLES(self): + return self.getToken(SqlBaseParser.ROLES, 0) + + def ROLLBACK(self): + return self.getToken(SqlBaseParser.ROLLBACK, 0) + + def ROLLUP(self): + return self.getToken(SqlBaseParser.ROLLUP, 0) + + def ROW(self): + return self.getToken(SqlBaseParser.ROW, 0) + + def ROWS(self): + return self.getToken(SqlBaseParser.ROWS, 0) + + def SCHEMA(self): + return self.getToken(SqlBaseParser.SCHEMA, 0) + + def SCHEMAS(self): + return self.getToken(SqlBaseParser.SCHEMAS, 0) + + def SECOND(self): + return self.getToken(SqlBaseParser.SECOND, 0) + + def SECONDS(self): + return self.getToken(SqlBaseParser.SECONDS, 0) + + def SEMI(self): + return self.getToken(SqlBaseParser.SEMI, 0) + + def SEPARATED(self): + return self.getToken(SqlBaseParser.SEPARATED, 0) + + def SERDE(self): + return self.getToken(SqlBaseParser.SERDE, 0) + + def SERDEPROPERTIES(self): + return self.getToken(SqlBaseParser.SERDEPROPERTIES, 0) + + def SET(self): + return self.getToken(SqlBaseParser.SET, 0) + + def SETMINUS(self): + return self.getToken(SqlBaseParser.SETMINUS, 0) + + def SETS(self): + return self.getToken(SqlBaseParser.SETS, 0) + + def SHOW(self): + return self.getToken(SqlBaseParser.SHOW, 0) + + def SKEWED(self): + return self.getToken(SqlBaseParser.SKEWED, 0) + + def SORT(self): + return self.getToken(SqlBaseParser.SORT, 0) + + def SORTED(self): + return self.getToken(SqlBaseParser.SORTED, 0) + + def SOURCE(self): + return self.getToken(SqlBaseParser.SOURCE, 0) + + def START(self): + return self.getToken(SqlBaseParser.START, 0) + + def STATISTICS(self): + return self.getToken(SqlBaseParser.STATISTICS, 0) + + def STORED(self): + return self.getToken(SqlBaseParser.STORED, 0) + + def STRATIFY(self): + return self.getToken(SqlBaseParser.STRATIFY, 0) + + def STRUCT(self): + return self.getToken(SqlBaseParser.STRUCT, 0) + + def SUBSTR(self): + return self.getToken(SqlBaseParser.SUBSTR, 0) + + def SUBSTRING(self): + return self.getToken(SqlBaseParser.SUBSTRING, 0) + + def SYNC(self): + return self.getToken(SqlBaseParser.SYNC, 0) + + def SYSTEM_TIME(self): + return self.getToken(SqlBaseParser.SYSTEM_TIME, 0) + + def SYSTEM_VERSION(self): + return self.getToken(SqlBaseParser.SYSTEM_VERSION, 0) + + def TABLES(self): + return self.getToken(SqlBaseParser.TABLES, 0) + + def TABLESAMPLE(self): + return self.getToken(SqlBaseParser.TABLESAMPLE, 0) + + def TARGET(self): + return self.getToken(SqlBaseParser.TARGET, 0) + + def TBLPROPERTIES(self): + return self.getToken(SqlBaseParser.TBLPROPERTIES, 0) + + def TEMPORARY(self): + return self.getToken(SqlBaseParser.TEMPORARY, 0) + + def TERMINATED(self): + return self.getToken(SqlBaseParser.TERMINATED, 0) + + def TIMESTAMP(self): + return self.getToken(SqlBaseParser.TIMESTAMP, 0) + + def TIMESTAMPADD(self): + return self.getToken(SqlBaseParser.TIMESTAMPADD, 0) + + def TIMESTAMPDIFF(self): + return self.getToken(SqlBaseParser.TIMESTAMPDIFF, 0) + + def TOUCH(self): + return self.getToken(SqlBaseParser.TOUCH, 0) + + def TRANSACTION(self): + return self.getToken(SqlBaseParser.TRANSACTION, 0) + + def TRANSACTIONS(self): + return self.getToken(SqlBaseParser.TRANSACTIONS, 0) + + def TRANSFORM(self): + return self.getToken(SqlBaseParser.TRANSFORM, 0) + + def TRIM(self): + return self.getToken(SqlBaseParser.TRIM, 0) + + def TRUE(self): + return self.getToken(SqlBaseParser.TRUE, 0) + + def TRUNCATE(self): + return self.getToken(SqlBaseParser.TRUNCATE, 0) + + def TRY_CAST(self): + return self.getToken(SqlBaseParser.TRY_CAST, 0) + + def TYPE(self): + return self.getToken(SqlBaseParser.TYPE, 0) + + def UNARCHIVE(self): + return self.getToken(SqlBaseParser.UNARCHIVE, 0) + + def UNBOUNDED(self): + return self.getToken(SqlBaseParser.UNBOUNDED, 0) + + def UNCACHE(self): + return self.getToken(SqlBaseParser.UNCACHE, 0) + + def UNLOCK(self): + return self.getToken(SqlBaseParser.UNLOCK, 0) + + def UNPIVOT(self): + return self.getToken(SqlBaseParser.UNPIVOT, 0) + + def UNSET(self): + return self.getToken(SqlBaseParser.UNSET, 0) + + def UPDATE(self): + return self.getToken(SqlBaseParser.UPDATE, 0) + + def USE(self): + return self.getToken(SqlBaseParser.USE, 0) + + def VALUES(self): + return self.getToken(SqlBaseParser.VALUES, 0) + + def VERSION(self): + return self.getToken(SqlBaseParser.VERSION, 0) + + def VIEW(self): + return self.getToken(SqlBaseParser.VIEW, 0) + + def VIEWS(self): + return self.getToken(SqlBaseParser.VIEWS, 0) + + def WEEK(self): + return self.getToken(SqlBaseParser.WEEK, 0) + + def WEEKS(self): + return self.getToken(SqlBaseParser.WEEKS, 0) + + def WINDOW(self): + return self.getToken(SqlBaseParser.WINDOW, 0) + + def YEAR(self): + return self.getToken(SqlBaseParser.YEAR, 0) + + def YEARS(self): + return self.getToken(SqlBaseParser.YEARS, 0) + + def ZONE(self): + return self.getToken(SqlBaseParser.ZONE, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_ansiNonReserved + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterAnsiNonReserved" ): + listener.enterAnsiNonReserved(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitAnsiNonReserved" ): + listener.exitAnsiNonReserved(self) + + + + + def ansiNonReserved(self): + + localctx = SqlBaseParser.AnsiNonReservedContext(self, self._ctx, self.state) + self.enterRule(localctx, 344, self.RULE_ansiNonReserved) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 3542 + _la = self._input.LA(1) + if not((((_la) & ~0x3f) == 0 and ((1 << _la) & -2191012289037026560) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & -4906136928869974017) != 0) or ((((_la - 128)) & ~0x3f) == 0 and ((1 << (_la - 128)) & -677567443547206837) != 0) or ((((_la - 192)) & ~0x3f) == 0 and ((1 << (_la - 192)) & -4576167932199175) != 0) or ((((_la - 256)) & ~0x3f) == 0 and ((1 << (_la - 256)) & 4028402566609403) != 0)): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class StrictNonReservedContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def ANTI(self): + return self.getToken(SqlBaseParser.ANTI, 0) + + def CROSS(self): + return self.getToken(SqlBaseParser.CROSS, 0) + + def EXCEPT(self): + return self.getToken(SqlBaseParser.EXCEPT, 0) + + def FULL(self): + return self.getToken(SqlBaseParser.FULL, 0) + + def INNER(self): + return self.getToken(SqlBaseParser.INNER, 0) + + def INTERSECT(self): + return self.getToken(SqlBaseParser.INTERSECT, 0) + + def JOIN(self): + return self.getToken(SqlBaseParser.JOIN, 0) + + def LATERAL(self): + return self.getToken(SqlBaseParser.LATERAL, 0) + + def LEFT(self): + return self.getToken(SqlBaseParser.LEFT, 0) + + def NATURAL(self): + return self.getToken(SqlBaseParser.NATURAL, 0) + + def ON(self): + return self.getToken(SqlBaseParser.ON, 0) + + def RIGHT(self): + return self.getToken(SqlBaseParser.RIGHT, 0) + + def SEMI(self): + return self.getToken(SqlBaseParser.SEMI, 0) + + def SETMINUS(self): + return self.getToken(SqlBaseParser.SETMINUS, 0) + + def UNION(self): + return self.getToken(SqlBaseParser.UNION, 0) + + def USING(self): + return self.getToken(SqlBaseParser.USING, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_strictNonReserved + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterStrictNonReserved" ): + listener.enterStrictNonReserved(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitStrictNonReserved" ): + listener.exitStrictNonReserved(self) + + + + + def strictNonReserved(self): + + localctx = SqlBaseParser.StrictNonReservedContext(self, self._ctx, self.state) + self.enterRule(localctx, 346, self.RULE_strictNonReserved) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 3544 + _la = self._input.LA(1) + if not(_la==15 or _la==54 or ((((_la - 87)) & ~0x3f) == 0 and ((1 << (_la - 87)) & 20557019150811137) != 0) or ((((_la - 170)) & ~0x3f) == 0 and ((1 << (_la - 170)) & 2251799813685377) != 0) or ((((_la - 234)) & ~0x3f) == 0 and ((1 << (_la - 234)) & 577586652210266177) != 0)): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class NonReservedContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def ADD(self): + return self.getToken(SqlBaseParser.ADD, 0) + + def AFTER(self): + return self.getToken(SqlBaseParser.AFTER, 0) + + def ALL(self): + return self.getToken(SqlBaseParser.ALL, 0) + + def ALTER(self): + return self.getToken(SqlBaseParser.ALTER, 0) + + def ALWAYS(self): + return self.getToken(SqlBaseParser.ALWAYS, 0) + + def ANALYZE(self): + return self.getToken(SqlBaseParser.ANALYZE, 0) + + def AND(self): + return self.getToken(SqlBaseParser.AND, 0) + + def ANY(self): + return self.getToken(SqlBaseParser.ANY, 0) + + def ANY_VALUE(self): + return self.getToken(SqlBaseParser.ANY_VALUE, 0) + + def ARCHIVE(self): + return self.getToken(SqlBaseParser.ARCHIVE, 0) + + def ARRAY(self): + return self.getToken(SqlBaseParser.ARRAY, 0) + + def AS(self): + return self.getToken(SqlBaseParser.AS, 0) + + def ASC(self): + return self.getToken(SqlBaseParser.ASC, 0) + + def AT(self): + return self.getToken(SqlBaseParser.AT, 0) + + def AUTHORIZATION(self): + return self.getToken(SqlBaseParser.AUTHORIZATION, 0) + + def BETWEEN(self): + return self.getToken(SqlBaseParser.BETWEEN, 0) + + def BOTH(self): + return self.getToken(SqlBaseParser.BOTH, 0) + + def BUCKET(self): + return self.getToken(SqlBaseParser.BUCKET, 0) + + def BUCKETS(self): + return self.getToken(SqlBaseParser.BUCKETS, 0) + + def BY(self): + return self.getToken(SqlBaseParser.BY, 0) + + def CACHE(self): + return self.getToken(SqlBaseParser.CACHE, 0) + + def CASCADE(self): + return self.getToken(SqlBaseParser.CASCADE, 0) + + def CASE(self): + return self.getToken(SqlBaseParser.CASE, 0) + + def CAST(self): + return self.getToken(SqlBaseParser.CAST, 0) + + def CATALOG(self): + return self.getToken(SqlBaseParser.CATALOG, 0) + + def CATALOGS(self): + return self.getToken(SqlBaseParser.CATALOGS, 0) + + def CHANGE(self): + return self.getToken(SqlBaseParser.CHANGE, 0) + + def CHECK(self): + return self.getToken(SqlBaseParser.CHECK, 0) + + def CLEAR(self): + return self.getToken(SqlBaseParser.CLEAR, 0) + + def CLUSTER(self): + return self.getToken(SqlBaseParser.CLUSTER, 0) + + def CLUSTERED(self): + return self.getToken(SqlBaseParser.CLUSTERED, 0) + + def CODEGEN(self): + return self.getToken(SqlBaseParser.CODEGEN, 0) + + def COLLATE(self): + return self.getToken(SqlBaseParser.COLLATE, 0) + + def COLLECTION(self): + return self.getToken(SqlBaseParser.COLLECTION, 0) + + def COLUMN(self): + return self.getToken(SqlBaseParser.COLUMN, 0) + + def COLUMNS(self): + return self.getToken(SqlBaseParser.COLUMNS, 0) + + def COMMENT(self): + return self.getToken(SqlBaseParser.COMMENT, 0) + + def COMMIT(self): + return self.getToken(SqlBaseParser.COMMIT, 0) + + def COMPACT(self): + return self.getToken(SqlBaseParser.COMPACT, 0) + + def COMPACTIONS(self): + return self.getToken(SqlBaseParser.COMPACTIONS, 0) + + def COMPUTE(self): + return self.getToken(SqlBaseParser.COMPUTE, 0) + + def CONCATENATE(self): + return self.getToken(SqlBaseParser.CONCATENATE, 0) + + def CONSTRAINT(self): + return self.getToken(SqlBaseParser.CONSTRAINT, 0) + + def COST(self): + return self.getToken(SqlBaseParser.COST, 0) + + def CREATE(self): + return self.getToken(SqlBaseParser.CREATE, 0) + + def CUBE(self): + return self.getToken(SqlBaseParser.CUBE, 0) + + def CURRENT(self): + return self.getToken(SqlBaseParser.CURRENT, 0) + + def CURRENT_DATE(self): + return self.getToken(SqlBaseParser.CURRENT_DATE, 0) + + def CURRENT_TIME(self): + return self.getToken(SqlBaseParser.CURRENT_TIME, 0) + + def CURRENT_TIMESTAMP(self): + return self.getToken(SqlBaseParser.CURRENT_TIMESTAMP, 0) + + def CURRENT_USER(self): + return self.getToken(SqlBaseParser.CURRENT_USER, 0) + + def DATA(self): + return self.getToken(SqlBaseParser.DATA, 0) + + def DATABASE(self): + return self.getToken(SqlBaseParser.DATABASE, 0) + + def DATABASES(self): + return self.getToken(SqlBaseParser.DATABASES, 0) + + def DATEADD(self): + return self.getToken(SqlBaseParser.DATEADD, 0) + + def DATEDIFF(self): + return self.getToken(SqlBaseParser.DATEDIFF, 0) + + def DAY(self): + return self.getToken(SqlBaseParser.DAY, 0) + + def DAYS(self): + return self.getToken(SqlBaseParser.DAYS, 0) + + def DAYOFYEAR(self): + return self.getToken(SqlBaseParser.DAYOFYEAR, 0) + + def DBPROPERTIES(self): + return self.getToken(SqlBaseParser.DBPROPERTIES, 0) + + def DEFAULT(self): + return self.getToken(SqlBaseParser.DEFAULT, 0) + + def DEFINED(self): + return self.getToken(SqlBaseParser.DEFINED, 0) + + def DELETE(self): + return self.getToken(SqlBaseParser.DELETE, 0) + + def DELIMITED(self): + return self.getToken(SqlBaseParser.DELIMITED, 0) + + def DESC(self): + return self.getToken(SqlBaseParser.DESC, 0) + + def DESCRIBE(self): + return self.getToken(SqlBaseParser.DESCRIBE, 0) + + def DFS(self): + return self.getToken(SqlBaseParser.DFS, 0) + + def DIRECTORIES(self): + return self.getToken(SqlBaseParser.DIRECTORIES, 0) + + def DIRECTORY(self): + return self.getToken(SqlBaseParser.DIRECTORY, 0) + + def DISTINCT(self): + return self.getToken(SqlBaseParser.DISTINCT, 0) + + def DISTRIBUTE(self): + return self.getToken(SqlBaseParser.DISTRIBUTE, 0) + + def DIV(self): + return self.getToken(SqlBaseParser.DIV, 0) + + def DROP(self): + return self.getToken(SqlBaseParser.DROP, 0) + + def ELSE(self): + return self.getToken(SqlBaseParser.ELSE, 0) + + def END(self): + return self.getToken(SqlBaseParser.END, 0) + + def ESCAPE(self): + return self.getToken(SqlBaseParser.ESCAPE, 0) + + def ESCAPED(self): + return self.getToken(SqlBaseParser.ESCAPED, 0) + + def EXCHANGE(self): + return self.getToken(SqlBaseParser.EXCHANGE, 0) + + def EXCLUDE(self): + return self.getToken(SqlBaseParser.EXCLUDE, 0) + + def EXISTS(self): + return self.getToken(SqlBaseParser.EXISTS, 0) + + def EXPLAIN(self): + return self.getToken(SqlBaseParser.EXPLAIN, 0) + + def EXPORT(self): + return self.getToken(SqlBaseParser.EXPORT, 0) + + def EXTENDED(self): + return self.getToken(SqlBaseParser.EXTENDED, 0) + + def EXTERNAL(self): + return self.getToken(SqlBaseParser.EXTERNAL, 0) + + def EXTRACT(self): + return self.getToken(SqlBaseParser.EXTRACT, 0) + + def FALSE(self): + return self.getToken(SqlBaseParser.FALSE, 0) + + def FETCH(self): + return self.getToken(SqlBaseParser.FETCH, 0) + + def FILTER(self): + return self.getToken(SqlBaseParser.FILTER, 0) + + def FIELDS(self): + return self.getToken(SqlBaseParser.FIELDS, 0) + + def FILEFORMAT(self): + return self.getToken(SqlBaseParser.FILEFORMAT, 0) + + def FIRST(self): + return self.getToken(SqlBaseParser.FIRST, 0) + + def FOLLOWING(self): + return self.getToken(SqlBaseParser.FOLLOWING, 0) + + def FOR(self): + return self.getToken(SqlBaseParser.FOR, 0) + + def FOREIGN(self): + return self.getToken(SqlBaseParser.FOREIGN, 0) + + def FORMAT(self): + return self.getToken(SqlBaseParser.FORMAT, 0) + + def FORMATTED(self): + return self.getToken(SqlBaseParser.FORMATTED, 0) + + def FROM(self): + return self.getToken(SqlBaseParser.FROM, 0) + + def FUNCTION(self): + return self.getToken(SqlBaseParser.FUNCTION, 0) + + def FUNCTIONS(self): + return self.getToken(SqlBaseParser.FUNCTIONS, 0) + + def GENERATED(self): + return self.getToken(SqlBaseParser.GENERATED, 0) + + def GLOBAL(self): + return self.getToken(SqlBaseParser.GLOBAL, 0) + + def GRANT(self): + return self.getToken(SqlBaseParser.GRANT, 0) + + def GROUP(self): + return self.getToken(SqlBaseParser.GROUP, 0) + + def GROUPING(self): + return self.getToken(SqlBaseParser.GROUPING, 0) + + def HAVING(self): + return self.getToken(SqlBaseParser.HAVING, 0) + + def HOUR(self): + return self.getToken(SqlBaseParser.HOUR, 0) + + def HOURS(self): + return self.getToken(SqlBaseParser.HOURS, 0) + + def IF(self): + return self.getToken(SqlBaseParser.IF, 0) + + def IGNORE(self): + return self.getToken(SqlBaseParser.IGNORE, 0) + + def IMPORT(self): + return self.getToken(SqlBaseParser.IMPORT, 0) + + def IN(self): + return self.getToken(SqlBaseParser.IN, 0) + + def INCLUDE(self): + return self.getToken(SqlBaseParser.INCLUDE, 0) + + def INDEX(self): + return self.getToken(SqlBaseParser.INDEX, 0) + + def INDEXES(self): + return self.getToken(SqlBaseParser.INDEXES, 0) + + def INPATH(self): + return self.getToken(SqlBaseParser.INPATH, 0) + + def INPUTFORMAT(self): + return self.getToken(SqlBaseParser.INPUTFORMAT, 0) + + def INSERT(self): + return self.getToken(SqlBaseParser.INSERT, 0) + + def INTERVAL(self): + return self.getToken(SqlBaseParser.INTERVAL, 0) + + def INTO(self): + return self.getToken(SqlBaseParser.INTO, 0) + + def IS(self): + return self.getToken(SqlBaseParser.IS, 0) + + def ITEMS(self): + return self.getToken(SqlBaseParser.ITEMS, 0) + + def KEYS(self): + return self.getToken(SqlBaseParser.KEYS, 0) + + def LAST(self): + return self.getToken(SqlBaseParser.LAST, 0) + + def LAZY(self): + return self.getToken(SqlBaseParser.LAZY, 0) + + def LEADING(self): + return self.getToken(SqlBaseParser.LEADING, 0) + + def LIKE(self): + return self.getToken(SqlBaseParser.LIKE, 0) + + def ILIKE(self): + return self.getToken(SqlBaseParser.ILIKE, 0) + + def LIMIT(self): + return self.getToken(SqlBaseParser.LIMIT, 0) + + def LINES(self): + return self.getToken(SqlBaseParser.LINES, 0) + + def LIST(self): + return self.getToken(SqlBaseParser.LIST, 0) + + def LOAD(self): + return self.getToken(SqlBaseParser.LOAD, 0) + + def LOCAL(self): + return self.getToken(SqlBaseParser.LOCAL, 0) + + def LOCATION(self): + return self.getToken(SqlBaseParser.LOCATION, 0) + + def LOCK(self): + return self.getToken(SqlBaseParser.LOCK, 0) + + def LOCKS(self): + return self.getToken(SqlBaseParser.LOCKS, 0) + + def LOGICAL(self): + return self.getToken(SqlBaseParser.LOGICAL, 0) + + def MACRO(self): + return self.getToken(SqlBaseParser.MACRO, 0) + + def MAP(self): + return self.getToken(SqlBaseParser.MAP, 0) + + def MATCHED(self): + return self.getToken(SqlBaseParser.MATCHED, 0) + + def MERGE(self): + return self.getToken(SqlBaseParser.MERGE, 0) + + def MICROSECOND(self): + return self.getToken(SqlBaseParser.MICROSECOND, 0) + + def MICROSECONDS(self): + return self.getToken(SqlBaseParser.MICROSECONDS, 0) + + def MILLISECOND(self): + return self.getToken(SqlBaseParser.MILLISECOND, 0) + + def MILLISECONDS(self): + return self.getToken(SqlBaseParser.MILLISECONDS, 0) + + def MINUTE(self): + return self.getToken(SqlBaseParser.MINUTE, 0) + + def MINUTES(self): + return self.getToken(SqlBaseParser.MINUTES, 0) + + def MONTH(self): + return self.getToken(SqlBaseParser.MONTH, 0) + + def MONTHS(self): + return self.getToken(SqlBaseParser.MONTHS, 0) + + def MSCK(self): + return self.getToken(SqlBaseParser.MSCK, 0) + + def NAMESPACE(self): + return self.getToken(SqlBaseParser.NAMESPACE, 0) + + def NAMESPACES(self): + return self.getToken(SqlBaseParser.NAMESPACES, 0) + + def NANOSECOND(self): + return self.getToken(SqlBaseParser.NANOSECOND, 0) + + def NANOSECONDS(self): + return self.getToken(SqlBaseParser.NANOSECONDS, 0) + + def NO(self): + return self.getToken(SqlBaseParser.NO, 0) + + def NOT(self): + return self.getToken(SqlBaseParser.NOT, 0) + + def NULL(self): + return self.getToken(SqlBaseParser.NULL, 0) + + def NULLS(self): + return self.getToken(SqlBaseParser.NULLS, 0) + + def OF(self): + return self.getToken(SqlBaseParser.OF, 0) + + def OFFSET(self): + return self.getToken(SqlBaseParser.OFFSET, 0) + + def ONLY(self): + return self.getToken(SqlBaseParser.ONLY, 0) + + def OPTION(self): + return self.getToken(SqlBaseParser.OPTION, 0) + + def OPTIONS(self): + return self.getToken(SqlBaseParser.OPTIONS, 0) + + def OR(self): + return self.getToken(SqlBaseParser.OR, 0) + + def ORDER(self): + return self.getToken(SqlBaseParser.ORDER, 0) + + def OUT(self): + return self.getToken(SqlBaseParser.OUT, 0) + + def OUTER(self): + return self.getToken(SqlBaseParser.OUTER, 0) + + def OUTPUTFORMAT(self): + return self.getToken(SqlBaseParser.OUTPUTFORMAT, 0) + + def OVER(self): + return self.getToken(SqlBaseParser.OVER, 0) + + def OVERLAPS(self): + return self.getToken(SqlBaseParser.OVERLAPS, 0) + + def OVERLAY(self): + return self.getToken(SqlBaseParser.OVERLAY, 0) + + def OVERWRITE(self): + return self.getToken(SqlBaseParser.OVERWRITE, 0) + + def PARTITION(self): + return self.getToken(SqlBaseParser.PARTITION, 0) + + def PARTITIONED(self): + return self.getToken(SqlBaseParser.PARTITIONED, 0) + + def PARTITIONS(self): + return self.getToken(SqlBaseParser.PARTITIONS, 0) + + def PERCENTILE_CONT(self): + return self.getToken(SqlBaseParser.PERCENTILE_CONT, 0) + + def PERCENTILE_DISC(self): + return self.getToken(SqlBaseParser.PERCENTILE_DISC, 0) + + def PERCENTLIT(self): + return self.getToken(SqlBaseParser.PERCENTLIT, 0) + + def PIVOT(self): + return self.getToken(SqlBaseParser.PIVOT, 0) + + def PLACING(self): + return self.getToken(SqlBaseParser.PLACING, 0) + + def POSITION(self): + return self.getToken(SqlBaseParser.POSITION, 0) + + def PRECEDING(self): + return self.getToken(SqlBaseParser.PRECEDING, 0) + + def PRIMARY(self): + return self.getToken(SqlBaseParser.PRIMARY, 0) + + def PRINCIPALS(self): + return self.getToken(SqlBaseParser.PRINCIPALS, 0) + + def PROPERTIES(self): + return self.getToken(SqlBaseParser.PROPERTIES, 0) + + def PURGE(self): + return self.getToken(SqlBaseParser.PURGE, 0) + + def QUARTER(self): + return self.getToken(SqlBaseParser.QUARTER, 0) + + def QUERY(self): + return self.getToken(SqlBaseParser.QUERY, 0) + + def RANGE(self): + return self.getToken(SqlBaseParser.RANGE, 0) + + def RECORDREADER(self): + return self.getToken(SqlBaseParser.RECORDREADER, 0) + + def RECORDWRITER(self): + return self.getToken(SqlBaseParser.RECORDWRITER, 0) + + def RECOVER(self): + return self.getToken(SqlBaseParser.RECOVER, 0) + + def REDUCE(self): + return self.getToken(SqlBaseParser.REDUCE, 0) + + def REFERENCES(self): + return self.getToken(SqlBaseParser.REFERENCES, 0) + + def REFRESH(self): + return self.getToken(SqlBaseParser.REFRESH, 0) + + def RENAME(self): + return self.getToken(SqlBaseParser.RENAME, 0) + + def REPAIR(self): + return self.getToken(SqlBaseParser.REPAIR, 0) + + def REPEATABLE(self): + return self.getToken(SqlBaseParser.REPEATABLE, 0) + + def REPLACE(self): + return self.getToken(SqlBaseParser.REPLACE, 0) + + def RESET(self): + return self.getToken(SqlBaseParser.RESET, 0) + + def RESPECT(self): + return self.getToken(SqlBaseParser.RESPECT, 0) + + def RESTRICT(self): + return self.getToken(SqlBaseParser.RESTRICT, 0) + + def REVOKE(self): + return self.getToken(SqlBaseParser.REVOKE, 0) + + def RLIKE(self): + return self.getToken(SqlBaseParser.RLIKE, 0) + + def ROLE(self): + return self.getToken(SqlBaseParser.ROLE, 0) + + def ROLES(self): + return self.getToken(SqlBaseParser.ROLES, 0) + + def ROLLBACK(self): + return self.getToken(SqlBaseParser.ROLLBACK, 0) + + def ROLLUP(self): + return self.getToken(SqlBaseParser.ROLLUP, 0) + + def ROW(self): + return self.getToken(SqlBaseParser.ROW, 0) + + def ROWS(self): + return self.getToken(SqlBaseParser.ROWS, 0) + + def SCHEMA(self): + return self.getToken(SqlBaseParser.SCHEMA, 0) + + def SCHEMAS(self): + return self.getToken(SqlBaseParser.SCHEMAS, 0) + + def SECOND(self): + return self.getToken(SqlBaseParser.SECOND, 0) + + def SECONDS(self): + return self.getToken(SqlBaseParser.SECONDS, 0) + + def SELECT(self): + return self.getToken(SqlBaseParser.SELECT, 0) + + def SEPARATED(self): + return self.getToken(SqlBaseParser.SEPARATED, 0) + + def SERDE(self): + return self.getToken(SqlBaseParser.SERDE, 0) + + def SERDEPROPERTIES(self): + return self.getToken(SqlBaseParser.SERDEPROPERTIES, 0) + + def SESSION_USER(self): + return self.getToken(SqlBaseParser.SESSION_USER, 0) + + def SET(self): + return self.getToken(SqlBaseParser.SET, 0) + + def SETS(self): + return self.getToken(SqlBaseParser.SETS, 0) + + def SHOW(self): + return self.getToken(SqlBaseParser.SHOW, 0) + + def SKEWED(self): + return self.getToken(SqlBaseParser.SKEWED, 0) + + def SOME(self): + return self.getToken(SqlBaseParser.SOME, 0) + + def SORT(self): + return self.getToken(SqlBaseParser.SORT, 0) + + def SORTED(self): + return self.getToken(SqlBaseParser.SORTED, 0) + + def SOURCE(self): + return self.getToken(SqlBaseParser.SOURCE, 0) + + def START(self): + return self.getToken(SqlBaseParser.START, 0) + + def STATISTICS(self): + return self.getToken(SqlBaseParser.STATISTICS, 0) + + def STORED(self): + return self.getToken(SqlBaseParser.STORED, 0) + + def STRATIFY(self): + return self.getToken(SqlBaseParser.STRATIFY, 0) + + def STRUCT(self): + return self.getToken(SqlBaseParser.STRUCT, 0) + + def SUBSTR(self): + return self.getToken(SqlBaseParser.SUBSTR, 0) + + def SUBSTRING(self): + return self.getToken(SqlBaseParser.SUBSTRING, 0) + + def SYNC(self): + return self.getToken(SqlBaseParser.SYNC, 0) + + def SYSTEM_TIME(self): + return self.getToken(SqlBaseParser.SYSTEM_TIME, 0) + + def SYSTEM_VERSION(self): + return self.getToken(SqlBaseParser.SYSTEM_VERSION, 0) + + def TABLE(self): + return self.getToken(SqlBaseParser.TABLE, 0) + + def TABLES(self): + return self.getToken(SqlBaseParser.TABLES, 0) + + def TABLESAMPLE(self): + return self.getToken(SqlBaseParser.TABLESAMPLE, 0) + + def TARGET(self): + return self.getToken(SqlBaseParser.TARGET, 0) + + def TBLPROPERTIES(self): + return self.getToken(SqlBaseParser.TBLPROPERTIES, 0) + + def TEMPORARY(self): + return self.getToken(SqlBaseParser.TEMPORARY, 0) + + def TERMINATED(self): + return self.getToken(SqlBaseParser.TERMINATED, 0) + + def THEN(self): + return self.getToken(SqlBaseParser.THEN, 0) + + def TIME(self): + return self.getToken(SqlBaseParser.TIME, 0) + + def TIMESTAMP(self): + return self.getToken(SqlBaseParser.TIMESTAMP, 0) + + def TIMESTAMPADD(self): + return self.getToken(SqlBaseParser.TIMESTAMPADD, 0) + + def TIMESTAMPDIFF(self): + return self.getToken(SqlBaseParser.TIMESTAMPDIFF, 0) + + def TO(self): + return self.getToken(SqlBaseParser.TO, 0) + + def TOUCH(self): + return self.getToken(SqlBaseParser.TOUCH, 0) + + def TRAILING(self): + return self.getToken(SqlBaseParser.TRAILING, 0) + + def TRANSACTION(self): + return self.getToken(SqlBaseParser.TRANSACTION, 0) + + def TRANSACTIONS(self): + return self.getToken(SqlBaseParser.TRANSACTIONS, 0) + + def TRANSFORM(self): + return self.getToken(SqlBaseParser.TRANSFORM, 0) + + def TRIM(self): + return self.getToken(SqlBaseParser.TRIM, 0) + + def TRUE(self): + return self.getToken(SqlBaseParser.TRUE, 0) + + def TRUNCATE(self): + return self.getToken(SqlBaseParser.TRUNCATE, 0) + + def TRY_CAST(self): + return self.getToken(SqlBaseParser.TRY_CAST, 0) + + def TYPE(self): + return self.getToken(SqlBaseParser.TYPE, 0) + + def UNARCHIVE(self): + return self.getToken(SqlBaseParser.UNARCHIVE, 0) + + def UNBOUNDED(self): + return self.getToken(SqlBaseParser.UNBOUNDED, 0) + + def UNCACHE(self): + return self.getToken(SqlBaseParser.UNCACHE, 0) + + def UNIQUE(self): + return self.getToken(SqlBaseParser.UNIQUE, 0) + + def UNKNOWN(self): + return self.getToken(SqlBaseParser.UNKNOWN, 0) + + def UNLOCK(self): + return self.getToken(SqlBaseParser.UNLOCK, 0) + + def UNPIVOT(self): + return self.getToken(SqlBaseParser.UNPIVOT, 0) + + def UNSET(self): + return self.getToken(SqlBaseParser.UNSET, 0) + + def UPDATE(self): + return self.getToken(SqlBaseParser.UPDATE, 0) + + def USE(self): + return self.getToken(SqlBaseParser.USE, 0) + + def USER(self): + return self.getToken(SqlBaseParser.USER, 0) + + def VALUES(self): + return self.getToken(SqlBaseParser.VALUES, 0) + + def VERSION(self): + return self.getToken(SqlBaseParser.VERSION, 0) + + def VIEW(self): + return self.getToken(SqlBaseParser.VIEW, 0) + + def VIEWS(self): + return self.getToken(SqlBaseParser.VIEWS, 0) + + def WEEK(self): + return self.getToken(SqlBaseParser.WEEK, 0) + + def WEEKS(self): + return self.getToken(SqlBaseParser.WEEKS, 0) + + def WHEN(self): + return self.getToken(SqlBaseParser.WHEN, 0) + + def WHERE(self): + return self.getToken(SqlBaseParser.WHERE, 0) + + def WINDOW(self): + return self.getToken(SqlBaseParser.WINDOW, 0) + + def WITH(self): + return self.getToken(SqlBaseParser.WITH, 0) + + def WITHIN(self): + return self.getToken(SqlBaseParser.WITHIN, 0) + + def YEAR(self): + return self.getToken(SqlBaseParser.YEAR, 0) + + def YEARS(self): + return self.getToken(SqlBaseParser.YEARS, 0) + + def ZONE(self): + return self.getToken(SqlBaseParser.ZONE, 0) + + def getRuleIndex(self): + return SqlBaseParser.RULE_nonReserved + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterNonReserved" ): + listener.enterNonReserved(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitNonReserved" ): + listener.exitNonReserved(self) + + + + + def nonReserved(self): + + localctx = SqlBaseParser.NonReservedContext(self, self._ctx, self.state) + self.enterRule(localctx, 348, self.RULE_nonReserved) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 3546 + _la = self._input.LA(1) + if not((((_la) & ~0x3f) == 0 and ((1 << _la) & -18014398509515008) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & -4611703610621820929) != 0) or ((((_la - 128)) & ~0x3f) == 0 and ((1 << (_la - 128)) & -567347999941765) != 0) or ((((_la - 192)) & ~0x3f) == 0 and ((1 << (_la - 192)) & -285873560092673) != 0) or ((((_la - 256)) & ~0x3f) == 0 and ((1 << (_la - 256)) & 4503461919981567) != 0)): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + + def sempred(self, localctx:RuleContext, ruleIndex:int, predIndex:int): + if self._predicates == None: + self._predicates = dict() + self._predicates[44] = self.queryTerm_sempred + self._predicates[120] = self.booleanExpression_sempred + self._predicates[122] = self.valueExpression_sempred + self._predicates[124] = self.primaryExpression_sempred + pred = self._predicates.get(ruleIndex, None) + if pred is None: + raise Exception("No predicate with index:" + str(ruleIndex)) + else: + return pred(localctx, predIndex) + + def queryTerm_sempred(self, localctx:QueryTermContext, predIndex:int): + if predIndex == 0: + return self.precpred(self._ctx, 1) + + + def booleanExpression_sempred(self, localctx:BooleanExpressionContext, predIndex:int): + if predIndex == 1: + return self.precpred(self._ctx, 2) + + + if predIndex == 2: + return self.precpred(self._ctx, 1) + + + def valueExpression_sempred(self, localctx:ValueExpressionContext, predIndex:int): + if predIndex == 3: + return self.precpred(self._ctx, 6) + + + if predIndex == 4: + return self.precpred(self._ctx, 5) + + + if predIndex == 5: + return self.precpred(self._ctx, 4) + + + if predIndex == 6: + return self.precpred(self._ctx, 3) + + + if predIndex == 7: + return self.precpred(self._ctx, 2) + + + if predIndex == 8: + return self.precpred(self._ctx, 1) + + + def primaryExpression_sempred(self, localctx:PrimaryExpressionContext, predIndex:int): + if predIndex == 9: + return self.precpred(self._ctx, 9) + + + if predIndex == 10: + return self.precpred(self._ctx, 7) + + + + + diff --git a/datajunction-server/datajunction_server/sql/parsing/backends/grammar/generated/SqlBaseParser.tokens b/datajunction-server/datajunction_server/sql/parsing/backends/grammar/generated/SqlBaseParser.tokens new file mode 100644 index 000000000..c76d66e32 --- /dev/null +++ b/datajunction-server/datajunction_server/sql/parsing/backends/grammar/generated/SqlBaseParser.tokens @@ -0,0 +1,669 @@ +SEMICOLON=1 +LEFT_PAREN=2 +RIGHT_PAREN=3 +COMMA=4 +DOT=5 +LEFT_BRACKET=6 +RIGHT_BRACKET=7 +ADD=8 +AFTER=9 +ALL=10 +ALTER=11 +ALWAYS=12 +ANALYZE=13 +AND=14 +ANTI=15 +ANY=16 +ANY_VALUE=17 +ARCHIVE=18 +ARRAY=19 +AS=20 +ASC=21 +AT=22 +AUTHORIZATION=23 +BETWEEN=24 +BOTH=25 +BUCKET=26 +BUCKETS=27 +BY=28 +CACHE=29 +CASCADE=30 +CASE=31 +CAST=32 +CATALOG=33 +CATALOGS=34 +CHANGE=35 +CHECK=36 +CLEAR=37 +CLUSTER=38 +CLUSTERED=39 +CODEGEN=40 +COLLATE=41 +COLLECTION=42 +COLUMN=43 +COLUMNS=44 +COMMENT=45 +COMMIT=46 +COMPACT=47 +COMPACTIONS=48 +COMPUTE=49 +CONCATENATE=50 +CONSTRAINT=51 +COST=52 +CREATE=53 +CROSS=54 +CUBE=55 +CURRENT=56 +CURRENT_DATE=57 +CURRENT_TIME=58 +CURRENT_TIMESTAMP=59 +CURRENT_USER=60 +DAY=61 +DAYS=62 +DAYOFYEAR=63 +DATA=64 +DATABASE=65 +DATABASES=66 +DATEADD=67 +DATEDIFF=68 +DBPROPERTIES=69 +DEFAULT=70 +DEFINED=71 +DELETE=72 +DELIMITED=73 +DESC=74 +DESCRIBE=75 +DFS=76 +DIRECTORIES=77 +DIRECTORY=78 +DISTINCT=79 +DISTRIBUTE=80 +DIV=81 +DROP=82 +ELSE=83 +END=84 +ESCAPE=85 +ESCAPED=86 +EXCEPT=87 +EXCHANGE=88 +EXCLUDE=89 +EXISTS=90 +EXPLAIN=91 +EXPORT=92 +EXTENDED=93 +EXTERNAL=94 +EXTRACT=95 +FALSE=96 +FETCH=97 +FIELDS=98 +FILTER=99 +FILEFORMAT=100 +FIRST=101 +FOLLOWING=102 +FOR=103 +FOREIGN=104 +FORMAT=105 +FORMATTED=106 +FROM=107 +FULL=108 +FUNCTION=109 +FUNCTIONS=110 +GENERATED=111 +GLOBAL=112 +GRANT=113 +GROUP=114 +GROUPING=115 +HAVING=116 +HOUR=117 +HOURS=118 +IF=119 +IGNORE=120 +IMPORT=121 +IN=122 +INCLUDE=123 +INDEX=124 +INDEXES=125 +INNER=126 +INPATH=127 +INPUTFORMAT=128 +INSERT=129 +INTERSECT=130 +INTERVAL=131 +INTO=132 +IS=133 +ITEMS=134 +JOIN=135 +KEYS=136 +LAST=137 +LATERAL=138 +LAZY=139 +LEADING=140 +LEFT=141 +LIKE=142 +ILIKE=143 +LIMIT=144 +LINES=145 +LIST=146 +LOAD=147 +LOCAL=148 +LOCATION=149 +LOCK=150 +LOCKS=151 +LOGICAL=152 +MACRO=153 +MAP=154 +MATCHED=155 +MERGE=156 +MICROSECOND=157 +MICROSECONDS=158 +MILLISECOND=159 +MILLISECONDS=160 +MINUTE=161 +MINUTES=162 +MONTH=163 +MONTHS=164 +MSCK=165 +NAMESPACE=166 +NAMESPACES=167 +NANOSECOND=168 +NANOSECONDS=169 +NATURAL=170 +NO=171 +NOT=172 +NULL=173 +NULLS=174 +OF=175 +OFFSET=176 +ON=177 +ONLY=178 +OPTION=179 +OPTIONS=180 +OR=181 +ORDER=182 +OUT=183 +OUTER=184 +OUTPUTFORMAT=185 +OVER=186 +OVERLAPS=187 +OVERLAY=188 +OVERWRITE=189 +PARTITION=190 +PARTITIONED=191 +PARTITIONS=192 +PERCENTILE_CONT=193 +PERCENTILE_DISC=194 +PERCENTLIT=195 +PIVOT=196 +PLACING=197 +POSITION=198 +PRECEDING=199 +PRIMARY=200 +PRINCIPALS=201 +PROPERTIES=202 +PURGE=203 +QUARTER=204 +QUERY=205 +RANGE=206 +RECORDREADER=207 +RECORDWRITER=208 +RECOVER=209 +REDUCE=210 +REFERENCES=211 +REFRESH=212 +RENAME=213 +REPAIR=214 +REPEATABLE=215 +REPLACE=216 +RESET=217 +RESPECT=218 +RESTRICT=219 +REVOKE=220 +RIGHT=221 +RLIKE=222 +ROLE=223 +ROLES=224 +ROLLBACK=225 +ROLLUP=226 +ROW=227 +ROWS=228 +SECOND=229 +SECONDS=230 +SCHEMA=231 +SCHEMAS=232 +SELECT=233 +SEMI=234 +SEPARATED=235 +SERDE=236 +SERDEPROPERTIES=237 +SESSION_USER=238 +SET=239 +SETMINUS=240 +SETS=241 +SHOW=242 +SKEWED=243 +SOME=244 +SORT=245 +SORTED=246 +SOURCE=247 +START=248 +STATISTICS=249 +STORED=250 +STRATIFY=251 +STRUCT=252 +SUBSTR=253 +SUBSTRING=254 +SYNC=255 +SYSTEM_TIME=256 +SYSTEM_VERSION=257 +TABLE=258 +TABLES=259 +TABLESAMPLE=260 +TARGET=261 +TBLPROPERTIES=262 +TEMPORARY=263 +TERMINATED=264 +THEN=265 +TIME=266 +TIMESTAMP=267 +TIMESTAMPADD=268 +TIMESTAMPDIFF=269 +TO=270 +TOUCH=271 +TRAILING=272 +TRANSACTION=273 +TRANSACTIONS=274 +TRANSFORM=275 +TRIM=276 +TRUE=277 +TRUNCATE=278 +TRY_CAST=279 +TYPE=280 +UNARCHIVE=281 +UNBOUNDED=282 +UNCACHE=283 +UNION=284 +UNIQUE=285 +UNKNOWN=286 +UNLOCK=287 +UNPIVOT=288 +UNSET=289 +UPDATE=290 +USE=291 +USER=292 +USING=293 +VALUES=294 +VERSION=295 +VIEW=296 +VIEWS=297 +WEEK=298 +WEEKS=299 +WHEN=300 +WHERE=301 +WINDOW=302 +WITH=303 +WITHIN=304 +YEAR=305 +YEARS=306 +ZONE=307 +EQ=308 +NSEQ=309 +NEQ=310 +NEQJ=311 +LT=312 +LTE=313 +GT=314 +GTE=315 +PLUS=316 +MINUS=317 +ASTERISK=318 +SLASH=319 +PERCENT=320 +TILDE=321 +AMPERSAND=322 +PIPE=323 +CONCAT_PIPE=324 +HAT=325 +COLON=326 +ARROW=327 +HENT_START=328 +HENT_END=329 +STRING=330 +DOUBLEQUOTED_STRING=331 +BIGINT_LITERAL=332 +SMALLINT_LITERAL=333 +TINYINT_LITERAL=334 +INTEGER_VALUE=335 +EXPONENT_VALUE=336 +DECIMAL_VALUE=337 +FLOAT_LITERAL=338 +DOUBLE_LITERAL=339 +BIGDECIMAL_LITERAL=340 +IDENTIFIER=341 +BACKQUOTED_IDENTIFIER=342 +SIMPLE_COMMENT=343 +BRACKETED_COMMENT=344 +WS=345 +UNRECOGNIZED=346 +';'=1 +'('=2 +')'=3 +','=4 +'.'=5 +'['=6 +']'=7 +'ADD'=8 +'AFTER'=9 +'ALL'=10 +'ALTER'=11 +'ALWAYS'=12 +'ANALYZE'=13 +'AND'=14 +'ANTI'=15 +'ANY'=16 +'ANY_VALUE'=17 +'ARCHIVE'=18 +'ARRAY'=19 +'AS'=20 +'ASC'=21 +'AT'=22 +'AUTHORIZATION'=23 +'BETWEEN'=24 +'BOTH'=25 +'BUCKET'=26 +'BUCKETS'=27 +'BY'=28 +'CACHE'=29 +'CASCADE'=30 +'CASE'=31 +'CAST'=32 +'CATALOG'=33 +'CATALOGS'=34 +'CHANGE'=35 +'CHECK'=36 +'CLEAR'=37 +'CLUSTER'=38 +'CLUSTERED'=39 +'CODEGEN'=40 +'COLLATE'=41 +'COLLECTION'=42 +'COLUMN'=43 +'COLUMNS'=44 +'COMMENT'=45 +'COMMIT'=46 +'COMPACT'=47 +'COMPACTIONS'=48 +'COMPUTE'=49 +'CONCATENATE'=50 +'CONSTRAINT'=51 +'COST'=52 +'CREATE'=53 +'CROSS'=54 +'CUBE'=55 +'CURRENT'=56 +'CURRENT_DATE'=57 +'CURRENT_TIME'=58 +'CURRENT_TIMESTAMP'=59 +'CURRENT_USER'=60 +'DAY'=61 +'DAYS'=62 +'DAYOFYEAR'=63 +'DATA'=64 +'DATABASE'=65 +'DATABASES'=66 +'DATEADD'=67 +'DATEDIFF'=68 +'DBPROPERTIES'=69 +'DEFAULT'=70 +'DEFINED'=71 +'DELETE'=72 +'DELIMITED'=73 +'DESC'=74 +'DESCRIBE'=75 +'DFS'=76 +'DIRECTORIES'=77 +'DIRECTORY'=78 +'DISTINCT'=79 +'DISTRIBUTE'=80 +'DIV'=81 +'DROP'=82 +'ELSE'=83 +'END'=84 +'ESCAPE'=85 +'ESCAPED'=86 +'EXCEPT'=87 +'EXCHANGE'=88 +'EXCLUDE'=89 +'EXISTS'=90 +'EXPLAIN'=91 +'EXPORT'=92 +'EXTENDED'=93 +'EXTERNAL'=94 +'EXTRACT'=95 +'FALSE'=96 +'FETCH'=97 +'FIELDS'=98 +'FILTER'=99 +'FILEFORMAT'=100 +'FIRST'=101 +'FOLLOWING'=102 +'FOR'=103 +'FOREIGN'=104 +'FORMAT'=105 +'FORMATTED'=106 +'FROM'=107 +'FULL'=108 +'FUNCTION'=109 +'FUNCTIONS'=110 +'GENERATED'=111 +'GLOBAL'=112 +'GRANT'=113 +'GROUP'=114 +'GROUPING'=115 +'HAVING'=116 +'HOUR'=117 +'HOURS'=118 +'IF'=119 +'IGNORE'=120 +'IMPORT'=121 +'IN'=122 +'INCLUDE'=123 +'INDEX'=124 +'INDEXES'=125 +'INNER'=126 +'INPATH'=127 +'INPUTFORMAT'=128 +'INSERT'=129 +'INTERSECT'=130 +'INTERVAL'=131 +'INTO'=132 +'IS'=133 +'ITEMS'=134 +'JOIN'=135 +'KEYS'=136 +'LAST'=137 +'LATERAL'=138 +'LAZY'=139 +'LEADING'=140 +'LEFT'=141 +'LIKE'=142 +'ILIKE'=143 +'LIMIT'=144 +'LINES'=145 +'LIST'=146 +'LOAD'=147 +'LOCAL'=148 +'LOCATION'=149 +'LOCK'=150 +'LOCKS'=151 +'LOGICAL'=152 +'MACRO'=153 +'MAP'=154 +'MATCHED'=155 +'MERGE'=156 +'MICROSECOND'=157 +'MICROSECONDS'=158 +'MILLISECOND'=159 +'MILLISECONDS'=160 +'MINUTE'=161 +'MINUTES'=162 +'MONTH'=163 +'MONTHS'=164 +'MSCK'=165 +'NAMESPACE'=166 +'NAMESPACES'=167 +'NANOSECOND'=168 +'NANOSECONDS'=169 +'NATURAL'=170 +'NO'=171 +'NULL'=173 +'NULLS'=174 +'OF'=175 +'OFFSET'=176 +'ON'=177 +'ONLY'=178 +'OPTION'=179 +'OPTIONS'=180 +'OR'=181 +'ORDER'=182 +'OUT'=183 +'OUTER'=184 +'OUTPUTFORMAT'=185 +'OVER'=186 +'OVERLAPS'=187 +'OVERLAY'=188 +'OVERWRITE'=189 +'PARTITION'=190 +'PARTITIONED'=191 +'PARTITIONS'=192 +'PERCENTILE_CONT'=193 +'PERCENTILE_DISC'=194 +'PERCENT'=195 +'PIVOT'=196 +'PLACING'=197 +'POSITION'=198 +'PRECEDING'=199 +'PRIMARY'=200 +'PRINCIPALS'=201 +'PROPERTIES'=202 +'PURGE'=203 +'QUARTER'=204 +'QUERY'=205 +'RANGE'=206 +'RECORDREADER'=207 +'RECORDWRITER'=208 +'RECOVER'=209 +'REDUCE'=210 +'REFERENCES'=211 +'REFRESH'=212 +'RENAME'=213 +'REPAIR'=214 +'REPEATABLE'=215 +'REPLACE'=216 +'RESET'=217 +'RESPECT'=218 +'RESTRICT'=219 +'REVOKE'=220 +'RIGHT'=221 +'ROLE'=223 +'ROLES'=224 +'ROLLBACK'=225 +'ROLLUP'=226 +'ROW'=227 +'ROWS'=228 +'SECOND'=229 +'SECONDS'=230 +'SCHEMA'=231 +'SCHEMAS'=232 +'SELECT'=233 +'SEMI'=234 +'SEPARATED'=235 +'SERDE'=236 +'SERDEPROPERTIES'=237 +'SESSION_USER'=238 +'SET'=239 +'MINUS'=240 +'SETS'=241 +'SHOW'=242 +'SKEWED'=243 +'SOME'=244 +'SORT'=245 +'SORTED'=246 +'SOURCE'=247 +'START'=248 +'STATISTICS'=249 +'STORED'=250 +'STRATIFY'=251 +'STRUCT'=252 +'SUBSTR'=253 +'SUBSTRING'=254 +'SYNC'=255 +'SYSTEM_TIME'=256 +'SYSTEM_VERSION'=257 +'TABLE'=258 +'TABLES'=259 +'TABLESAMPLE'=260 +'TARGET'=261 +'TBLPROPERTIES'=262 +'TERMINATED'=264 +'THEN'=265 +'TIME'=266 +'TIMESTAMP'=267 +'TIMESTAMPADD'=268 +'TIMESTAMPDIFF'=269 +'TO'=270 +'TOUCH'=271 +'TRAILING'=272 +'TRANSACTION'=273 +'TRANSACTIONS'=274 +'TRANSFORM'=275 +'TRIM'=276 +'TRUE'=277 +'TRUNCATE'=278 +'TRY_CAST'=279 +'TYPE'=280 +'UNARCHIVE'=281 +'UNBOUNDED'=282 +'UNCACHE'=283 +'UNION'=284 +'UNIQUE'=285 +'UNKNOWN'=286 +'UNLOCK'=287 +'UNPIVOT'=288 +'UNSET'=289 +'UPDATE'=290 +'USE'=291 +'USER'=292 +'USING'=293 +'VALUES'=294 +'VERSION'=295 +'VIEW'=296 +'VIEWS'=297 +'WEEK'=298 +'WEEKS'=299 +'WHEN'=300 +'WHERE'=301 +'WINDOW'=302 +'WITH'=303 +'WITHIN'=304 +'YEAR'=305 +'YEARS'=306 +'ZONE'=307 +'<=>'=309 +'<>'=310 +'!='=311 +'<'=312 +'>'=314 +'+'=316 +'-'=317 +'*'=318 +'/'=319 +'%'=320 +'~'=321 +'&'=322 +'|'=323 +'||'=324 +'^'=325 +':'=326 +'->'=327 +'/*+'=328 +'*/'=329 diff --git a/datajunction-server/datajunction_server/sql/parsing/backends/grammar/generated/SqlBaseParserListener.py b/datajunction-server/datajunction_server/sql/parsing/backends/grammar/generated/SqlBaseParserListener.py new file mode 100644 index 000000000..7bc9fe002 --- /dev/null +++ b/datajunction-server/datajunction_server/sql/parsing/backends/grammar/generated/SqlBaseParserListener.py @@ -0,0 +1,2919 @@ +# Generated from SqlBaseParser.g4 by ANTLR 4.12.0 +from antlr4 import * +if __name__ is not None and "." in __name__: + from .SqlBaseParser import SqlBaseParser +else: + from SqlBaseParser import SqlBaseParser + +# This class defines a complete listener for a parse tree produced by SqlBaseParser. +class SqlBaseParserListener(ParseTreeListener): + + # Enter a parse tree produced by SqlBaseParser#singleStatement. + def enterSingleStatement(self, ctx:SqlBaseParser.SingleStatementContext): + pass + + # Exit a parse tree produced by SqlBaseParser#singleStatement. + def exitSingleStatement(self, ctx:SqlBaseParser.SingleStatementContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#singleExpression. + def enterSingleExpression(self, ctx:SqlBaseParser.SingleExpressionContext): + pass + + # Exit a parse tree produced by SqlBaseParser#singleExpression. + def exitSingleExpression(self, ctx:SqlBaseParser.SingleExpressionContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#singleTableIdentifier. + def enterSingleTableIdentifier(self, ctx:SqlBaseParser.SingleTableIdentifierContext): + pass + + # Exit a parse tree produced by SqlBaseParser#singleTableIdentifier. + def exitSingleTableIdentifier(self, ctx:SqlBaseParser.SingleTableIdentifierContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#singleMultipartIdentifier. + def enterSingleMultipartIdentifier(self, ctx:SqlBaseParser.SingleMultipartIdentifierContext): + pass + + # Exit a parse tree produced by SqlBaseParser#singleMultipartIdentifier. + def exitSingleMultipartIdentifier(self, ctx:SqlBaseParser.SingleMultipartIdentifierContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#singleFunctionIdentifier. + def enterSingleFunctionIdentifier(self, ctx:SqlBaseParser.SingleFunctionIdentifierContext): + pass + + # Exit a parse tree produced by SqlBaseParser#singleFunctionIdentifier. + def exitSingleFunctionIdentifier(self, ctx:SqlBaseParser.SingleFunctionIdentifierContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#singleDataType. + def enterSingleDataType(self, ctx:SqlBaseParser.SingleDataTypeContext): + pass + + # Exit a parse tree produced by SqlBaseParser#singleDataType. + def exitSingleDataType(self, ctx:SqlBaseParser.SingleDataTypeContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#singleTableSchema. + def enterSingleTableSchema(self, ctx:SqlBaseParser.SingleTableSchemaContext): + pass + + # Exit a parse tree produced by SqlBaseParser#singleTableSchema. + def exitSingleTableSchema(self, ctx:SqlBaseParser.SingleTableSchemaContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#statementDefault. + def enterStatementDefault(self, ctx:SqlBaseParser.StatementDefaultContext): + pass + + # Exit a parse tree produced by SqlBaseParser#statementDefault. + def exitStatementDefault(self, ctx:SqlBaseParser.StatementDefaultContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#dmlStatement. + def enterDmlStatement(self, ctx:SqlBaseParser.DmlStatementContext): + pass + + # Exit a parse tree produced by SqlBaseParser#dmlStatement. + def exitDmlStatement(self, ctx:SqlBaseParser.DmlStatementContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#use. + def enterUse(self, ctx:SqlBaseParser.UseContext): + pass + + # Exit a parse tree produced by SqlBaseParser#use. + def exitUse(self, ctx:SqlBaseParser.UseContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#useNamespace. + def enterUseNamespace(self, ctx:SqlBaseParser.UseNamespaceContext): + pass + + # Exit a parse tree produced by SqlBaseParser#useNamespace. + def exitUseNamespace(self, ctx:SqlBaseParser.UseNamespaceContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#setCatalog. + def enterSetCatalog(self, ctx:SqlBaseParser.SetCatalogContext): + pass + + # Exit a parse tree produced by SqlBaseParser#setCatalog. + def exitSetCatalog(self, ctx:SqlBaseParser.SetCatalogContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#createNamespace. + def enterCreateNamespace(self, ctx:SqlBaseParser.CreateNamespaceContext): + pass + + # Exit a parse tree produced by SqlBaseParser#createNamespace. + def exitCreateNamespace(self, ctx:SqlBaseParser.CreateNamespaceContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#setNamespaceProperties. + def enterSetNamespaceProperties(self, ctx:SqlBaseParser.SetNamespacePropertiesContext): + pass + + # Exit a parse tree produced by SqlBaseParser#setNamespaceProperties. + def exitSetNamespaceProperties(self, ctx:SqlBaseParser.SetNamespacePropertiesContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#setNamespaceLocation. + def enterSetNamespaceLocation(self, ctx:SqlBaseParser.SetNamespaceLocationContext): + pass + + # Exit a parse tree produced by SqlBaseParser#setNamespaceLocation. + def exitSetNamespaceLocation(self, ctx:SqlBaseParser.SetNamespaceLocationContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#dropNamespace. + def enterDropNamespace(self, ctx:SqlBaseParser.DropNamespaceContext): + pass + + # Exit a parse tree produced by SqlBaseParser#dropNamespace. + def exitDropNamespace(self, ctx:SqlBaseParser.DropNamespaceContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#showNamespaces. + def enterShowNamespaces(self, ctx:SqlBaseParser.ShowNamespacesContext): + pass + + # Exit a parse tree produced by SqlBaseParser#showNamespaces. + def exitShowNamespaces(self, ctx:SqlBaseParser.ShowNamespacesContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#createTable. + def enterCreateTable(self, ctx:SqlBaseParser.CreateTableContext): + pass + + # Exit a parse tree produced by SqlBaseParser#createTable. + def exitCreateTable(self, ctx:SqlBaseParser.CreateTableContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#createTableLike. + def enterCreateTableLike(self, ctx:SqlBaseParser.CreateTableLikeContext): + pass + + # Exit a parse tree produced by SqlBaseParser#createTableLike. + def exitCreateTableLike(self, ctx:SqlBaseParser.CreateTableLikeContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#replaceTable. + def enterReplaceTable(self, ctx:SqlBaseParser.ReplaceTableContext): + pass + + # Exit a parse tree produced by SqlBaseParser#replaceTable. + def exitReplaceTable(self, ctx:SqlBaseParser.ReplaceTableContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#analyze. + def enterAnalyze(self, ctx:SqlBaseParser.AnalyzeContext): + pass + + # Exit a parse tree produced by SqlBaseParser#analyze. + def exitAnalyze(self, ctx:SqlBaseParser.AnalyzeContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#analyzeTables. + def enterAnalyzeTables(self, ctx:SqlBaseParser.AnalyzeTablesContext): + pass + + # Exit a parse tree produced by SqlBaseParser#analyzeTables. + def exitAnalyzeTables(self, ctx:SqlBaseParser.AnalyzeTablesContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#addTableColumns. + def enterAddTableColumns(self, ctx:SqlBaseParser.AddTableColumnsContext): + pass + + # Exit a parse tree produced by SqlBaseParser#addTableColumns. + def exitAddTableColumns(self, ctx:SqlBaseParser.AddTableColumnsContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#renameTableColumn. + def enterRenameTableColumn(self, ctx:SqlBaseParser.RenameTableColumnContext): + pass + + # Exit a parse tree produced by SqlBaseParser#renameTableColumn. + def exitRenameTableColumn(self, ctx:SqlBaseParser.RenameTableColumnContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#dropTableColumns. + def enterDropTableColumns(self, ctx:SqlBaseParser.DropTableColumnsContext): + pass + + # Exit a parse tree produced by SqlBaseParser#dropTableColumns. + def exitDropTableColumns(self, ctx:SqlBaseParser.DropTableColumnsContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#renameTable. + def enterRenameTable(self, ctx:SqlBaseParser.RenameTableContext): + pass + + # Exit a parse tree produced by SqlBaseParser#renameTable. + def exitRenameTable(self, ctx:SqlBaseParser.RenameTableContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#setTableProperties. + def enterSetTableProperties(self, ctx:SqlBaseParser.SetTablePropertiesContext): + pass + + # Exit a parse tree produced by SqlBaseParser#setTableProperties. + def exitSetTableProperties(self, ctx:SqlBaseParser.SetTablePropertiesContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#unsetTableProperties. + def enterUnsetTableProperties(self, ctx:SqlBaseParser.UnsetTablePropertiesContext): + pass + + # Exit a parse tree produced by SqlBaseParser#unsetTableProperties. + def exitUnsetTableProperties(self, ctx:SqlBaseParser.UnsetTablePropertiesContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#alterTableAlterColumn. + def enterAlterTableAlterColumn(self, ctx:SqlBaseParser.AlterTableAlterColumnContext): + pass + + # Exit a parse tree produced by SqlBaseParser#alterTableAlterColumn. + def exitAlterTableAlterColumn(self, ctx:SqlBaseParser.AlterTableAlterColumnContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#hiveChangeColumn. + def enterHiveChangeColumn(self, ctx:SqlBaseParser.HiveChangeColumnContext): + pass + + # Exit a parse tree produced by SqlBaseParser#hiveChangeColumn. + def exitHiveChangeColumn(self, ctx:SqlBaseParser.HiveChangeColumnContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#hiveReplaceColumns. + def enterHiveReplaceColumns(self, ctx:SqlBaseParser.HiveReplaceColumnsContext): + pass + + # Exit a parse tree produced by SqlBaseParser#hiveReplaceColumns. + def exitHiveReplaceColumns(self, ctx:SqlBaseParser.HiveReplaceColumnsContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#setTableSerDe. + def enterSetTableSerDe(self, ctx:SqlBaseParser.SetTableSerDeContext): + pass + + # Exit a parse tree produced by SqlBaseParser#setTableSerDe. + def exitSetTableSerDe(self, ctx:SqlBaseParser.SetTableSerDeContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#addTablePartition. + def enterAddTablePartition(self, ctx:SqlBaseParser.AddTablePartitionContext): + pass + + # Exit a parse tree produced by SqlBaseParser#addTablePartition. + def exitAddTablePartition(self, ctx:SqlBaseParser.AddTablePartitionContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#renameTablePartition. + def enterRenameTablePartition(self, ctx:SqlBaseParser.RenameTablePartitionContext): + pass + + # Exit a parse tree produced by SqlBaseParser#renameTablePartition. + def exitRenameTablePartition(self, ctx:SqlBaseParser.RenameTablePartitionContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#dropTablePartitions. + def enterDropTablePartitions(self, ctx:SqlBaseParser.DropTablePartitionsContext): + pass + + # Exit a parse tree produced by SqlBaseParser#dropTablePartitions. + def exitDropTablePartitions(self, ctx:SqlBaseParser.DropTablePartitionsContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#setTableLocation. + def enterSetTableLocation(self, ctx:SqlBaseParser.SetTableLocationContext): + pass + + # Exit a parse tree produced by SqlBaseParser#setTableLocation. + def exitSetTableLocation(self, ctx:SqlBaseParser.SetTableLocationContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#recoverPartitions. + def enterRecoverPartitions(self, ctx:SqlBaseParser.RecoverPartitionsContext): + pass + + # Exit a parse tree produced by SqlBaseParser#recoverPartitions. + def exitRecoverPartitions(self, ctx:SqlBaseParser.RecoverPartitionsContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#dropTable. + def enterDropTable(self, ctx:SqlBaseParser.DropTableContext): + pass + + # Exit a parse tree produced by SqlBaseParser#dropTable. + def exitDropTable(self, ctx:SqlBaseParser.DropTableContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#dropView. + def enterDropView(self, ctx:SqlBaseParser.DropViewContext): + pass + + # Exit a parse tree produced by SqlBaseParser#dropView. + def exitDropView(self, ctx:SqlBaseParser.DropViewContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#createView. + def enterCreateView(self, ctx:SqlBaseParser.CreateViewContext): + pass + + # Exit a parse tree produced by SqlBaseParser#createView. + def exitCreateView(self, ctx:SqlBaseParser.CreateViewContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#createTempViewUsing. + def enterCreateTempViewUsing(self, ctx:SqlBaseParser.CreateTempViewUsingContext): + pass + + # Exit a parse tree produced by SqlBaseParser#createTempViewUsing. + def exitCreateTempViewUsing(self, ctx:SqlBaseParser.CreateTempViewUsingContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#alterViewQuery. + def enterAlterViewQuery(self, ctx:SqlBaseParser.AlterViewQueryContext): + pass + + # Exit a parse tree produced by SqlBaseParser#alterViewQuery. + def exitAlterViewQuery(self, ctx:SqlBaseParser.AlterViewQueryContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#createFunction. + def enterCreateFunction(self, ctx:SqlBaseParser.CreateFunctionContext): + pass + + # Exit a parse tree produced by SqlBaseParser#createFunction. + def exitCreateFunction(self, ctx:SqlBaseParser.CreateFunctionContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#dropFunction. + def enterDropFunction(self, ctx:SqlBaseParser.DropFunctionContext): + pass + + # Exit a parse tree produced by SqlBaseParser#dropFunction. + def exitDropFunction(self, ctx:SqlBaseParser.DropFunctionContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#explain. + def enterExplain(self, ctx:SqlBaseParser.ExplainContext): + pass + + # Exit a parse tree produced by SqlBaseParser#explain. + def exitExplain(self, ctx:SqlBaseParser.ExplainContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#showTables. + def enterShowTables(self, ctx:SqlBaseParser.ShowTablesContext): + pass + + # Exit a parse tree produced by SqlBaseParser#showTables. + def exitShowTables(self, ctx:SqlBaseParser.ShowTablesContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#showTableExtended. + def enterShowTableExtended(self, ctx:SqlBaseParser.ShowTableExtendedContext): + pass + + # Exit a parse tree produced by SqlBaseParser#showTableExtended. + def exitShowTableExtended(self, ctx:SqlBaseParser.ShowTableExtendedContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#showTblProperties. + def enterShowTblProperties(self, ctx:SqlBaseParser.ShowTblPropertiesContext): + pass + + # Exit a parse tree produced by SqlBaseParser#showTblProperties. + def exitShowTblProperties(self, ctx:SqlBaseParser.ShowTblPropertiesContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#showColumns. + def enterShowColumns(self, ctx:SqlBaseParser.ShowColumnsContext): + pass + + # Exit a parse tree produced by SqlBaseParser#showColumns. + def exitShowColumns(self, ctx:SqlBaseParser.ShowColumnsContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#showViews. + def enterShowViews(self, ctx:SqlBaseParser.ShowViewsContext): + pass + + # Exit a parse tree produced by SqlBaseParser#showViews. + def exitShowViews(self, ctx:SqlBaseParser.ShowViewsContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#showPartitions. + def enterShowPartitions(self, ctx:SqlBaseParser.ShowPartitionsContext): + pass + + # Exit a parse tree produced by SqlBaseParser#showPartitions. + def exitShowPartitions(self, ctx:SqlBaseParser.ShowPartitionsContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#showFunctions. + def enterShowFunctions(self, ctx:SqlBaseParser.ShowFunctionsContext): + pass + + # Exit a parse tree produced by SqlBaseParser#showFunctions. + def exitShowFunctions(self, ctx:SqlBaseParser.ShowFunctionsContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#showCreateTable. + def enterShowCreateTable(self, ctx:SqlBaseParser.ShowCreateTableContext): + pass + + # Exit a parse tree produced by SqlBaseParser#showCreateTable. + def exitShowCreateTable(self, ctx:SqlBaseParser.ShowCreateTableContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#showCurrentNamespace. + def enterShowCurrentNamespace(self, ctx:SqlBaseParser.ShowCurrentNamespaceContext): + pass + + # Exit a parse tree produced by SqlBaseParser#showCurrentNamespace. + def exitShowCurrentNamespace(self, ctx:SqlBaseParser.ShowCurrentNamespaceContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#showCatalogs. + def enterShowCatalogs(self, ctx:SqlBaseParser.ShowCatalogsContext): + pass + + # Exit a parse tree produced by SqlBaseParser#showCatalogs. + def exitShowCatalogs(self, ctx:SqlBaseParser.ShowCatalogsContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#describeFunction. + def enterDescribeFunction(self, ctx:SqlBaseParser.DescribeFunctionContext): + pass + + # Exit a parse tree produced by SqlBaseParser#describeFunction. + def exitDescribeFunction(self, ctx:SqlBaseParser.DescribeFunctionContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#describeNamespace. + def enterDescribeNamespace(self, ctx:SqlBaseParser.DescribeNamespaceContext): + pass + + # Exit a parse tree produced by SqlBaseParser#describeNamespace. + def exitDescribeNamespace(self, ctx:SqlBaseParser.DescribeNamespaceContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#describeRelation. + def enterDescribeRelation(self, ctx:SqlBaseParser.DescribeRelationContext): + pass + + # Exit a parse tree produced by SqlBaseParser#describeRelation. + def exitDescribeRelation(self, ctx:SqlBaseParser.DescribeRelationContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#describeQuery. + def enterDescribeQuery(self, ctx:SqlBaseParser.DescribeQueryContext): + pass + + # Exit a parse tree produced by SqlBaseParser#describeQuery. + def exitDescribeQuery(self, ctx:SqlBaseParser.DescribeQueryContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#commentNamespace. + def enterCommentNamespace(self, ctx:SqlBaseParser.CommentNamespaceContext): + pass + + # Exit a parse tree produced by SqlBaseParser#commentNamespace. + def exitCommentNamespace(self, ctx:SqlBaseParser.CommentNamespaceContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#commentTable. + def enterCommentTable(self, ctx:SqlBaseParser.CommentTableContext): + pass + + # Exit a parse tree produced by SqlBaseParser#commentTable. + def exitCommentTable(self, ctx:SqlBaseParser.CommentTableContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#refreshTable. + def enterRefreshTable(self, ctx:SqlBaseParser.RefreshTableContext): + pass + + # Exit a parse tree produced by SqlBaseParser#refreshTable. + def exitRefreshTable(self, ctx:SqlBaseParser.RefreshTableContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#refreshFunction. + def enterRefreshFunction(self, ctx:SqlBaseParser.RefreshFunctionContext): + pass + + # Exit a parse tree produced by SqlBaseParser#refreshFunction. + def exitRefreshFunction(self, ctx:SqlBaseParser.RefreshFunctionContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#refreshResource. + def enterRefreshResource(self, ctx:SqlBaseParser.RefreshResourceContext): + pass + + # Exit a parse tree produced by SqlBaseParser#refreshResource. + def exitRefreshResource(self, ctx:SqlBaseParser.RefreshResourceContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#cacheTable. + def enterCacheTable(self, ctx:SqlBaseParser.CacheTableContext): + pass + + # Exit a parse tree produced by SqlBaseParser#cacheTable. + def exitCacheTable(self, ctx:SqlBaseParser.CacheTableContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#uncacheTable. + def enterUncacheTable(self, ctx:SqlBaseParser.UncacheTableContext): + pass + + # Exit a parse tree produced by SqlBaseParser#uncacheTable. + def exitUncacheTable(self, ctx:SqlBaseParser.UncacheTableContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#clearCache. + def enterClearCache(self, ctx:SqlBaseParser.ClearCacheContext): + pass + + # Exit a parse tree produced by SqlBaseParser#clearCache. + def exitClearCache(self, ctx:SqlBaseParser.ClearCacheContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#loadData. + def enterLoadData(self, ctx:SqlBaseParser.LoadDataContext): + pass + + # Exit a parse tree produced by SqlBaseParser#loadData. + def exitLoadData(self, ctx:SqlBaseParser.LoadDataContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#truncateTable. + def enterTruncateTable(self, ctx:SqlBaseParser.TruncateTableContext): + pass + + # Exit a parse tree produced by SqlBaseParser#truncateTable. + def exitTruncateTable(self, ctx:SqlBaseParser.TruncateTableContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#repairTable. + def enterRepairTable(self, ctx:SqlBaseParser.RepairTableContext): + pass + + # Exit a parse tree produced by SqlBaseParser#repairTable. + def exitRepairTable(self, ctx:SqlBaseParser.RepairTableContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#manageResource. + def enterManageResource(self, ctx:SqlBaseParser.ManageResourceContext): + pass + + # Exit a parse tree produced by SqlBaseParser#manageResource. + def exitManageResource(self, ctx:SqlBaseParser.ManageResourceContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#failNativeCommand. + def enterFailNativeCommand(self, ctx:SqlBaseParser.FailNativeCommandContext): + pass + + # Exit a parse tree produced by SqlBaseParser#failNativeCommand. + def exitFailNativeCommand(self, ctx:SqlBaseParser.FailNativeCommandContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#setTimeZone. + def enterSetTimeZone(self, ctx:SqlBaseParser.SetTimeZoneContext): + pass + + # Exit a parse tree produced by SqlBaseParser#setTimeZone. + def exitSetTimeZone(self, ctx:SqlBaseParser.SetTimeZoneContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#setQuotedConfiguration. + def enterSetQuotedConfiguration(self, ctx:SqlBaseParser.SetQuotedConfigurationContext): + pass + + # Exit a parse tree produced by SqlBaseParser#setQuotedConfiguration. + def exitSetQuotedConfiguration(self, ctx:SqlBaseParser.SetQuotedConfigurationContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#setConfiguration. + def enterSetConfiguration(self, ctx:SqlBaseParser.SetConfigurationContext): + pass + + # Exit a parse tree produced by SqlBaseParser#setConfiguration. + def exitSetConfiguration(self, ctx:SqlBaseParser.SetConfigurationContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#resetQuotedConfiguration. + def enterResetQuotedConfiguration(self, ctx:SqlBaseParser.ResetQuotedConfigurationContext): + pass + + # Exit a parse tree produced by SqlBaseParser#resetQuotedConfiguration. + def exitResetQuotedConfiguration(self, ctx:SqlBaseParser.ResetQuotedConfigurationContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#resetConfiguration. + def enterResetConfiguration(self, ctx:SqlBaseParser.ResetConfigurationContext): + pass + + # Exit a parse tree produced by SqlBaseParser#resetConfiguration. + def exitResetConfiguration(self, ctx:SqlBaseParser.ResetConfigurationContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#createIndex. + def enterCreateIndex(self, ctx:SqlBaseParser.CreateIndexContext): + pass + + # Exit a parse tree produced by SqlBaseParser#createIndex. + def exitCreateIndex(self, ctx:SqlBaseParser.CreateIndexContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#dropIndex. + def enterDropIndex(self, ctx:SqlBaseParser.DropIndexContext): + pass + + # Exit a parse tree produced by SqlBaseParser#dropIndex. + def exitDropIndex(self, ctx:SqlBaseParser.DropIndexContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#timezone. + def enterTimezone(self, ctx:SqlBaseParser.TimezoneContext): + pass + + # Exit a parse tree produced by SqlBaseParser#timezone. + def exitTimezone(self, ctx:SqlBaseParser.TimezoneContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#configKey. + def enterConfigKey(self, ctx:SqlBaseParser.ConfigKeyContext): + pass + + # Exit a parse tree produced by SqlBaseParser#configKey. + def exitConfigKey(self, ctx:SqlBaseParser.ConfigKeyContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#configValue. + def enterConfigValue(self, ctx:SqlBaseParser.ConfigValueContext): + pass + + # Exit a parse tree produced by SqlBaseParser#configValue. + def exitConfigValue(self, ctx:SqlBaseParser.ConfigValueContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#unsupportedHiveNativeCommands. + def enterUnsupportedHiveNativeCommands(self, ctx:SqlBaseParser.UnsupportedHiveNativeCommandsContext): + pass + + # Exit a parse tree produced by SqlBaseParser#unsupportedHiveNativeCommands. + def exitUnsupportedHiveNativeCommands(self, ctx:SqlBaseParser.UnsupportedHiveNativeCommandsContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#createTableHeader. + def enterCreateTableHeader(self, ctx:SqlBaseParser.CreateTableHeaderContext): + pass + + # Exit a parse tree produced by SqlBaseParser#createTableHeader. + def exitCreateTableHeader(self, ctx:SqlBaseParser.CreateTableHeaderContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#replaceTableHeader. + def enterReplaceTableHeader(self, ctx:SqlBaseParser.ReplaceTableHeaderContext): + pass + + # Exit a parse tree produced by SqlBaseParser#replaceTableHeader. + def exitReplaceTableHeader(self, ctx:SqlBaseParser.ReplaceTableHeaderContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#bucketSpec. + def enterBucketSpec(self, ctx:SqlBaseParser.BucketSpecContext): + pass + + # Exit a parse tree produced by SqlBaseParser#bucketSpec. + def exitBucketSpec(self, ctx:SqlBaseParser.BucketSpecContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#skewSpec. + def enterSkewSpec(self, ctx:SqlBaseParser.SkewSpecContext): + pass + + # Exit a parse tree produced by SqlBaseParser#skewSpec. + def exitSkewSpec(self, ctx:SqlBaseParser.SkewSpecContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#locationSpec. + def enterLocationSpec(self, ctx:SqlBaseParser.LocationSpecContext): + pass + + # Exit a parse tree produced by SqlBaseParser#locationSpec. + def exitLocationSpec(self, ctx:SqlBaseParser.LocationSpecContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#commentSpec. + def enterCommentSpec(self, ctx:SqlBaseParser.CommentSpecContext): + pass + + # Exit a parse tree produced by SqlBaseParser#commentSpec. + def exitCommentSpec(self, ctx:SqlBaseParser.CommentSpecContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#query. + def enterQuery(self, ctx:SqlBaseParser.QueryContext): + pass + + # Exit a parse tree produced by SqlBaseParser#query. + def exitQuery(self, ctx:SqlBaseParser.QueryContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#insertOverwriteTable. + def enterInsertOverwriteTable(self, ctx:SqlBaseParser.InsertOverwriteTableContext): + pass + + # Exit a parse tree produced by SqlBaseParser#insertOverwriteTable. + def exitInsertOverwriteTable(self, ctx:SqlBaseParser.InsertOverwriteTableContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#insertIntoTable. + def enterInsertIntoTable(self, ctx:SqlBaseParser.InsertIntoTableContext): + pass + + # Exit a parse tree produced by SqlBaseParser#insertIntoTable. + def exitInsertIntoTable(self, ctx:SqlBaseParser.InsertIntoTableContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#insertIntoReplaceWhere. + def enterInsertIntoReplaceWhere(self, ctx:SqlBaseParser.InsertIntoReplaceWhereContext): + pass + + # Exit a parse tree produced by SqlBaseParser#insertIntoReplaceWhere. + def exitInsertIntoReplaceWhere(self, ctx:SqlBaseParser.InsertIntoReplaceWhereContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#insertOverwriteHiveDir. + def enterInsertOverwriteHiveDir(self, ctx:SqlBaseParser.InsertOverwriteHiveDirContext): + pass + + # Exit a parse tree produced by SqlBaseParser#insertOverwriteHiveDir. + def exitInsertOverwriteHiveDir(self, ctx:SqlBaseParser.InsertOverwriteHiveDirContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#insertOverwriteDir. + def enterInsertOverwriteDir(self, ctx:SqlBaseParser.InsertOverwriteDirContext): + pass + + # Exit a parse tree produced by SqlBaseParser#insertOverwriteDir. + def exitInsertOverwriteDir(self, ctx:SqlBaseParser.InsertOverwriteDirContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#partitionSpecLocation. + def enterPartitionSpecLocation(self, ctx:SqlBaseParser.PartitionSpecLocationContext): + pass + + # Exit a parse tree produced by SqlBaseParser#partitionSpecLocation. + def exitPartitionSpecLocation(self, ctx:SqlBaseParser.PartitionSpecLocationContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#partitionSpec. + def enterPartitionSpec(self, ctx:SqlBaseParser.PartitionSpecContext): + pass + + # Exit a parse tree produced by SqlBaseParser#partitionSpec. + def exitPartitionSpec(self, ctx:SqlBaseParser.PartitionSpecContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#partitionVal. + def enterPartitionVal(self, ctx:SqlBaseParser.PartitionValContext): + pass + + # Exit a parse tree produced by SqlBaseParser#partitionVal. + def exitPartitionVal(self, ctx:SqlBaseParser.PartitionValContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#namespace. + def enterNamespace(self, ctx:SqlBaseParser.NamespaceContext): + pass + + # Exit a parse tree produced by SqlBaseParser#namespace. + def exitNamespace(self, ctx:SqlBaseParser.NamespaceContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#namespaces. + def enterNamespaces(self, ctx:SqlBaseParser.NamespacesContext): + pass + + # Exit a parse tree produced by SqlBaseParser#namespaces. + def exitNamespaces(self, ctx:SqlBaseParser.NamespacesContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#describeFuncName. + def enterDescribeFuncName(self, ctx:SqlBaseParser.DescribeFuncNameContext): + pass + + # Exit a parse tree produced by SqlBaseParser#describeFuncName. + def exitDescribeFuncName(self, ctx:SqlBaseParser.DescribeFuncNameContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#describeColName. + def enterDescribeColName(self, ctx:SqlBaseParser.DescribeColNameContext): + pass + + # Exit a parse tree produced by SqlBaseParser#describeColName. + def exitDescribeColName(self, ctx:SqlBaseParser.DescribeColNameContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#ctes. + def enterCtes(self, ctx:SqlBaseParser.CtesContext): + pass + + # Exit a parse tree produced by SqlBaseParser#ctes. + def exitCtes(self, ctx:SqlBaseParser.CtesContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#namedQuery. + def enterNamedQuery(self, ctx:SqlBaseParser.NamedQueryContext): + pass + + # Exit a parse tree produced by SqlBaseParser#namedQuery. + def exitNamedQuery(self, ctx:SqlBaseParser.NamedQueryContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#tableProvider. + def enterTableProvider(self, ctx:SqlBaseParser.TableProviderContext): + pass + + # Exit a parse tree produced by SqlBaseParser#tableProvider. + def exitTableProvider(self, ctx:SqlBaseParser.TableProviderContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#createTableClauses. + def enterCreateTableClauses(self, ctx:SqlBaseParser.CreateTableClausesContext): + pass + + # Exit a parse tree produced by SqlBaseParser#createTableClauses. + def exitCreateTableClauses(self, ctx:SqlBaseParser.CreateTableClausesContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#propertyList. + def enterPropertyList(self, ctx:SqlBaseParser.PropertyListContext): + pass + + # Exit a parse tree produced by SqlBaseParser#propertyList. + def exitPropertyList(self, ctx:SqlBaseParser.PropertyListContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#property. + def enterProperty(self, ctx:SqlBaseParser.PropertyContext): + pass + + # Exit a parse tree produced by SqlBaseParser#property. + def exitProperty(self, ctx:SqlBaseParser.PropertyContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#propertyKey. + def enterPropertyKey(self, ctx:SqlBaseParser.PropertyKeyContext): + pass + + # Exit a parse tree produced by SqlBaseParser#propertyKey. + def exitPropertyKey(self, ctx:SqlBaseParser.PropertyKeyContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#propertyValue. + def enterPropertyValue(self, ctx:SqlBaseParser.PropertyValueContext): + pass + + # Exit a parse tree produced by SqlBaseParser#propertyValue. + def exitPropertyValue(self, ctx:SqlBaseParser.PropertyValueContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#constantList. + def enterConstantList(self, ctx:SqlBaseParser.ConstantListContext): + pass + + # Exit a parse tree produced by SqlBaseParser#constantList. + def exitConstantList(self, ctx:SqlBaseParser.ConstantListContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#nestedConstantList. + def enterNestedConstantList(self, ctx:SqlBaseParser.NestedConstantListContext): + pass + + # Exit a parse tree produced by SqlBaseParser#nestedConstantList. + def exitNestedConstantList(self, ctx:SqlBaseParser.NestedConstantListContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#createFileFormat. + def enterCreateFileFormat(self, ctx:SqlBaseParser.CreateFileFormatContext): + pass + + # Exit a parse tree produced by SqlBaseParser#createFileFormat. + def exitCreateFileFormat(self, ctx:SqlBaseParser.CreateFileFormatContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#tableFileFormat. + def enterTableFileFormat(self, ctx:SqlBaseParser.TableFileFormatContext): + pass + + # Exit a parse tree produced by SqlBaseParser#tableFileFormat. + def exitTableFileFormat(self, ctx:SqlBaseParser.TableFileFormatContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#genericFileFormat. + def enterGenericFileFormat(self, ctx:SqlBaseParser.GenericFileFormatContext): + pass + + # Exit a parse tree produced by SqlBaseParser#genericFileFormat. + def exitGenericFileFormat(self, ctx:SqlBaseParser.GenericFileFormatContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#storageHandler. + def enterStorageHandler(self, ctx:SqlBaseParser.StorageHandlerContext): + pass + + # Exit a parse tree produced by SqlBaseParser#storageHandler. + def exitStorageHandler(self, ctx:SqlBaseParser.StorageHandlerContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#resource. + def enterResource(self, ctx:SqlBaseParser.ResourceContext): + pass + + # Exit a parse tree produced by SqlBaseParser#resource. + def exitResource(self, ctx:SqlBaseParser.ResourceContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#singleInsertQuery. + def enterSingleInsertQuery(self, ctx:SqlBaseParser.SingleInsertQueryContext): + pass + + # Exit a parse tree produced by SqlBaseParser#singleInsertQuery. + def exitSingleInsertQuery(self, ctx:SqlBaseParser.SingleInsertQueryContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#multiInsertQuery. + def enterMultiInsertQuery(self, ctx:SqlBaseParser.MultiInsertQueryContext): + pass + + # Exit a parse tree produced by SqlBaseParser#multiInsertQuery. + def exitMultiInsertQuery(self, ctx:SqlBaseParser.MultiInsertQueryContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#deleteFromTable. + def enterDeleteFromTable(self, ctx:SqlBaseParser.DeleteFromTableContext): + pass + + # Exit a parse tree produced by SqlBaseParser#deleteFromTable. + def exitDeleteFromTable(self, ctx:SqlBaseParser.DeleteFromTableContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#updateTable. + def enterUpdateTable(self, ctx:SqlBaseParser.UpdateTableContext): + pass + + # Exit a parse tree produced by SqlBaseParser#updateTable. + def exitUpdateTable(self, ctx:SqlBaseParser.UpdateTableContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#mergeIntoTable. + def enterMergeIntoTable(self, ctx:SqlBaseParser.MergeIntoTableContext): + pass + + # Exit a parse tree produced by SqlBaseParser#mergeIntoTable. + def exitMergeIntoTable(self, ctx:SqlBaseParser.MergeIntoTableContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#queryOrganization. + def enterQueryOrganization(self, ctx:SqlBaseParser.QueryOrganizationContext): + pass + + # Exit a parse tree produced by SqlBaseParser#queryOrganization. + def exitQueryOrganization(self, ctx:SqlBaseParser.QueryOrganizationContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#multiInsertQueryBody. + def enterMultiInsertQueryBody(self, ctx:SqlBaseParser.MultiInsertQueryBodyContext): + pass + + # Exit a parse tree produced by SqlBaseParser#multiInsertQueryBody. + def exitMultiInsertQueryBody(self, ctx:SqlBaseParser.MultiInsertQueryBodyContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#queryTermDefault. + def enterQueryTermDefault(self, ctx:SqlBaseParser.QueryTermDefaultContext): + pass + + # Exit a parse tree produced by SqlBaseParser#queryTermDefault. + def exitQueryTermDefault(self, ctx:SqlBaseParser.QueryTermDefaultContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#setOperation. + def enterSetOperation(self, ctx:SqlBaseParser.SetOperationContext): + pass + + # Exit a parse tree produced by SqlBaseParser#setOperation. + def exitSetOperation(self, ctx:SqlBaseParser.SetOperationContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#queryPrimaryDefault. + def enterQueryPrimaryDefault(self, ctx:SqlBaseParser.QueryPrimaryDefaultContext): + pass + + # Exit a parse tree produced by SqlBaseParser#queryPrimaryDefault. + def exitQueryPrimaryDefault(self, ctx:SqlBaseParser.QueryPrimaryDefaultContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#fromStmt. + def enterFromStmt(self, ctx:SqlBaseParser.FromStmtContext): + pass + + # Exit a parse tree produced by SqlBaseParser#fromStmt. + def exitFromStmt(self, ctx:SqlBaseParser.FromStmtContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#table. + def enterTable(self, ctx:SqlBaseParser.TableContext): + pass + + # Exit a parse tree produced by SqlBaseParser#table. + def exitTable(self, ctx:SqlBaseParser.TableContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#inlineTableDefault1. + def enterInlineTableDefault1(self, ctx:SqlBaseParser.InlineTableDefault1Context): + pass + + # Exit a parse tree produced by SqlBaseParser#inlineTableDefault1. + def exitInlineTableDefault1(self, ctx:SqlBaseParser.InlineTableDefault1Context): + pass + + + # Enter a parse tree produced by SqlBaseParser#subquery. + def enterSubquery(self, ctx:SqlBaseParser.SubqueryContext): + pass + + # Exit a parse tree produced by SqlBaseParser#subquery. + def exitSubquery(self, ctx:SqlBaseParser.SubqueryContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#sortItem. + def enterSortItem(self, ctx:SqlBaseParser.SortItemContext): + pass + + # Exit a parse tree produced by SqlBaseParser#sortItem. + def exitSortItem(self, ctx:SqlBaseParser.SortItemContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#fromStatement. + def enterFromStatement(self, ctx:SqlBaseParser.FromStatementContext): + pass + + # Exit a parse tree produced by SqlBaseParser#fromStatement. + def exitFromStatement(self, ctx:SqlBaseParser.FromStatementContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#fromStatementBody. + def enterFromStatementBody(self, ctx:SqlBaseParser.FromStatementBodyContext): + pass + + # Exit a parse tree produced by SqlBaseParser#fromStatementBody. + def exitFromStatementBody(self, ctx:SqlBaseParser.FromStatementBodyContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#transformQuerySpecification. + def enterTransformQuerySpecification(self, ctx:SqlBaseParser.TransformQuerySpecificationContext): + pass + + # Exit a parse tree produced by SqlBaseParser#transformQuerySpecification. + def exitTransformQuerySpecification(self, ctx:SqlBaseParser.TransformQuerySpecificationContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#regularQuerySpecification. + def enterRegularQuerySpecification(self, ctx:SqlBaseParser.RegularQuerySpecificationContext): + pass + + # Exit a parse tree produced by SqlBaseParser#regularQuerySpecification. + def exitRegularQuerySpecification(self, ctx:SqlBaseParser.RegularQuerySpecificationContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#transformClause. + def enterTransformClause(self, ctx:SqlBaseParser.TransformClauseContext): + pass + + # Exit a parse tree produced by SqlBaseParser#transformClause. + def exitTransformClause(self, ctx:SqlBaseParser.TransformClauseContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#selectClause. + def enterSelectClause(self, ctx:SqlBaseParser.SelectClauseContext): + pass + + # Exit a parse tree produced by SqlBaseParser#selectClause. + def exitSelectClause(self, ctx:SqlBaseParser.SelectClauseContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#setClause. + def enterSetClause(self, ctx:SqlBaseParser.SetClauseContext): + pass + + # Exit a parse tree produced by SqlBaseParser#setClause. + def exitSetClause(self, ctx:SqlBaseParser.SetClauseContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#matchedClause. + def enterMatchedClause(self, ctx:SqlBaseParser.MatchedClauseContext): + pass + + # Exit a parse tree produced by SqlBaseParser#matchedClause. + def exitMatchedClause(self, ctx:SqlBaseParser.MatchedClauseContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#notMatchedClause. + def enterNotMatchedClause(self, ctx:SqlBaseParser.NotMatchedClauseContext): + pass + + # Exit a parse tree produced by SqlBaseParser#notMatchedClause. + def exitNotMatchedClause(self, ctx:SqlBaseParser.NotMatchedClauseContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#notMatchedBySourceClause. + def enterNotMatchedBySourceClause(self, ctx:SqlBaseParser.NotMatchedBySourceClauseContext): + pass + + # Exit a parse tree produced by SqlBaseParser#notMatchedBySourceClause. + def exitNotMatchedBySourceClause(self, ctx:SqlBaseParser.NotMatchedBySourceClauseContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#matchedAction. + def enterMatchedAction(self, ctx:SqlBaseParser.MatchedActionContext): + pass + + # Exit a parse tree produced by SqlBaseParser#matchedAction. + def exitMatchedAction(self, ctx:SqlBaseParser.MatchedActionContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#notMatchedAction. + def enterNotMatchedAction(self, ctx:SqlBaseParser.NotMatchedActionContext): + pass + + # Exit a parse tree produced by SqlBaseParser#notMatchedAction. + def exitNotMatchedAction(self, ctx:SqlBaseParser.NotMatchedActionContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#notMatchedBySourceAction. + def enterNotMatchedBySourceAction(self, ctx:SqlBaseParser.NotMatchedBySourceActionContext): + pass + + # Exit a parse tree produced by SqlBaseParser#notMatchedBySourceAction. + def exitNotMatchedBySourceAction(self, ctx:SqlBaseParser.NotMatchedBySourceActionContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#assignmentList. + def enterAssignmentList(self, ctx:SqlBaseParser.AssignmentListContext): + pass + + # Exit a parse tree produced by SqlBaseParser#assignmentList. + def exitAssignmentList(self, ctx:SqlBaseParser.AssignmentListContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#assignment. + def enterAssignment(self, ctx:SqlBaseParser.AssignmentContext): + pass + + # Exit a parse tree produced by SqlBaseParser#assignment. + def exitAssignment(self, ctx:SqlBaseParser.AssignmentContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#whereClause. + def enterWhereClause(self, ctx:SqlBaseParser.WhereClauseContext): + pass + + # Exit a parse tree produced by SqlBaseParser#whereClause. + def exitWhereClause(self, ctx:SqlBaseParser.WhereClauseContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#havingClause. + def enterHavingClause(self, ctx:SqlBaseParser.HavingClauseContext): + pass + + # Exit a parse tree produced by SqlBaseParser#havingClause. + def exitHavingClause(self, ctx:SqlBaseParser.HavingClauseContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#hint. + def enterHint(self, ctx:SqlBaseParser.HintContext): + pass + + # Exit a parse tree produced by SqlBaseParser#hint. + def exitHint(self, ctx:SqlBaseParser.HintContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#hintStatement. + def enterHintStatement(self, ctx:SqlBaseParser.HintStatementContext): + pass + + # Exit a parse tree produced by SqlBaseParser#hintStatement. + def exitHintStatement(self, ctx:SqlBaseParser.HintStatementContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#fromClause. + def enterFromClause(self, ctx:SqlBaseParser.FromClauseContext): + pass + + # Exit a parse tree produced by SqlBaseParser#fromClause. + def exitFromClause(self, ctx:SqlBaseParser.FromClauseContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#temporalClause. + def enterTemporalClause(self, ctx:SqlBaseParser.TemporalClauseContext): + pass + + # Exit a parse tree produced by SqlBaseParser#temporalClause. + def exitTemporalClause(self, ctx:SqlBaseParser.TemporalClauseContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#aggregationClause. + def enterAggregationClause(self, ctx:SqlBaseParser.AggregationClauseContext): + pass + + # Exit a parse tree produced by SqlBaseParser#aggregationClause. + def exitAggregationClause(self, ctx:SqlBaseParser.AggregationClauseContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#groupByClause. + def enterGroupByClause(self, ctx:SqlBaseParser.GroupByClauseContext): + pass + + # Exit a parse tree produced by SqlBaseParser#groupByClause. + def exitGroupByClause(self, ctx:SqlBaseParser.GroupByClauseContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#groupingAnalytics. + def enterGroupingAnalytics(self, ctx:SqlBaseParser.GroupingAnalyticsContext): + pass + + # Exit a parse tree produced by SqlBaseParser#groupingAnalytics. + def exitGroupingAnalytics(self, ctx:SqlBaseParser.GroupingAnalyticsContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#groupingElement. + def enterGroupingElement(self, ctx:SqlBaseParser.GroupingElementContext): + pass + + # Exit a parse tree produced by SqlBaseParser#groupingElement. + def exitGroupingElement(self, ctx:SqlBaseParser.GroupingElementContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#groupingSet. + def enterGroupingSet(self, ctx:SqlBaseParser.GroupingSetContext): + pass + + # Exit a parse tree produced by SqlBaseParser#groupingSet. + def exitGroupingSet(self, ctx:SqlBaseParser.GroupingSetContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#pivotClause. + def enterPivotClause(self, ctx:SqlBaseParser.PivotClauseContext): + pass + + # Exit a parse tree produced by SqlBaseParser#pivotClause. + def exitPivotClause(self, ctx:SqlBaseParser.PivotClauseContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#pivotColumn. + def enterPivotColumn(self, ctx:SqlBaseParser.PivotColumnContext): + pass + + # Exit a parse tree produced by SqlBaseParser#pivotColumn. + def exitPivotColumn(self, ctx:SqlBaseParser.PivotColumnContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#pivotValue. + def enterPivotValue(self, ctx:SqlBaseParser.PivotValueContext): + pass + + # Exit a parse tree produced by SqlBaseParser#pivotValue. + def exitPivotValue(self, ctx:SqlBaseParser.PivotValueContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#unpivotClause. + def enterUnpivotClause(self, ctx:SqlBaseParser.UnpivotClauseContext): + pass + + # Exit a parse tree produced by SqlBaseParser#unpivotClause. + def exitUnpivotClause(self, ctx:SqlBaseParser.UnpivotClauseContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#unpivotNullClause. + def enterUnpivotNullClause(self, ctx:SqlBaseParser.UnpivotNullClauseContext): + pass + + # Exit a parse tree produced by SqlBaseParser#unpivotNullClause. + def exitUnpivotNullClause(self, ctx:SqlBaseParser.UnpivotNullClauseContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#unpivotOperator. + def enterUnpivotOperator(self, ctx:SqlBaseParser.UnpivotOperatorContext): + pass + + # Exit a parse tree produced by SqlBaseParser#unpivotOperator. + def exitUnpivotOperator(self, ctx:SqlBaseParser.UnpivotOperatorContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#unpivotSingleValueColumnClause. + def enterUnpivotSingleValueColumnClause(self, ctx:SqlBaseParser.UnpivotSingleValueColumnClauseContext): + pass + + # Exit a parse tree produced by SqlBaseParser#unpivotSingleValueColumnClause. + def exitUnpivotSingleValueColumnClause(self, ctx:SqlBaseParser.UnpivotSingleValueColumnClauseContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#unpivotMultiValueColumnClause. + def enterUnpivotMultiValueColumnClause(self, ctx:SqlBaseParser.UnpivotMultiValueColumnClauseContext): + pass + + # Exit a parse tree produced by SqlBaseParser#unpivotMultiValueColumnClause. + def exitUnpivotMultiValueColumnClause(self, ctx:SqlBaseParser.UnpivotMultiValueColumnClauseContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#unpivotColumnSet. + def enterUnpivotColumnSet(self, ctx:SqlBaseParser.UnpivotColumnSetContext): + pass + + # Exit a parse tree produced by SqlBaseParser#unpivotColumnSet. + def exitUnpivotColumnSet(self, ctx:SqlBaseParser.UnpivotColumnSetContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#unpivotValueColumn. + def enterUnpivotValueColumn(self, ctx:SqlBaseParser.UnpivotValueColumnContext): + pass + + # Exit a parse tree produced by SqlBaseParser#unpivotValueColumn. + def exitUnpivotValueColumn(self, ctx:SqlBaseParser.UnpivotValueColumnContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#unpivotNameColumn. + def enterUnpivotNameColumn(self, ctx:SqlBaseParser.UnpivotNameColumnContext): + pass + + # Exit a parse tree produced by SqlBaseParser#unpivotNameColumn. + def exitUnpivotNameColumn(self, ctx:SqlBaseParser.UnpivotNameColumnContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#unpivotColumnAndAlias. + def enterUnpivotColumnAndAlias(self, ctx:SqlBaseParser.UnpivotColumnAndAliasContext): + pass + + # Exit a parse tree produced by SqlBaseParser#unpivotColumnAndAlias. + def exitUnpivotColumnAndAlias(self, ctx:SqlBaseParser.UnpivotColumnAndAliasContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#unpivotColumn. + def enterUnpivotColumn(self, ctx:SqlBaseParser.UnpivotColumnContext): + pass + + # Exit a parse tree produced by SqlBaseParser#unpivotColumn. + def exitUnpivotColumn(self, ctx:SqlBaseParser.UnpivotColumnContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#unpivotAlias. + def enterUnpivotAlias(self, ctx:SqlBaseParser.UnpivotAliasContext): + pass + + # Exit a parse tree produced by SqlBaseParser#unpivotAlias. + def exitUnpivotAlias(self, ctx:SqlBaseParser.UnpivotAliasContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#lateralView. + def enterLateralView(self, ctx:SqlBaseParser.LateralViewContext): + pass + + # Exit a parse tree produced by SqlBaseParser#lateralView. + def exitLateralView(self, ctx:SqlBaseParser.LateralViewContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#setQuantifier. + def enterSetQuantifier(self, ctx:SqlBaseParser.SetQuantifierContext): + pass + + # Exit a parse tree produced by SqlBaseParser#setQuantifier. + def exitSetQuantifier(self, ctx:SqlBaseParser.SetQuantifierContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#relation. + def enterRelation(self, ctx:SqlBaseParser.RelationContext): + pass + + # Exit a parse tree produced by SqlBaseParser#relation. + def exitRelation(self, ctx:SqlBaseParser.RelationContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#relationExtension. + def enterRelationExtension(self, ctx:SqlBaseParser.RelationExtensionContext): + pass + + # Exit a parse tree produced by SqlBaseParser#relationExtension. + def exitRelationExtension(self, ctx:SqlBaseParser.RelationExtensionContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#joinRelation. + def enterJoinRelation(self, ctx:SqlBaseParser.JoinRelationContext): + pass + + # Exit a parse tree produced by SqlBaseParser#joinRelation. + def exitJoinRelation(self, ctx:SqlBaseParser.JoinRelationContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#joinType. + def enterJoinType(self, ctx:SqlBaseParser.JoinTypeContext): + pass + + # Exit a parse tree produced by SqlBaseParser#joinType. + def exitJoinType(self, ctx:SqlBaseParser.JoinTypeContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#joinCriteria. + def enterJoinCriteria(self, ctx:SqlBaseParser.JoinCriteriaContext): + pass + + # Exit a parse tree produced by SqlBaseParser#joinCriteria. + def exitJoinCriteria(self, ctx:SqlBaseParser.JoinCriteriaContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#sample. + def enterSample(self, ctx:SqlBaseParser.SampleContext): + pass + + # Exit a parse tree produced by SqlBaseParser#sample. + def exitSample(self, ctx:SqlBaseParser.SampleContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#sampleByPercentile. + def enterSampleByPercentile(self, ctx:SqlBaseParser.SampleByPercentileContext): + pass + + # Exit a parse tree produced by SqlBaseParser#sampleByPercentile. + def exitSampleByPercentile(self, ctx:SqlBaseParser.SampleByPercentileContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#sampleByRows. + def enterSampleByRows(self, ctx:SqlBaseParser.SampleByRowsContext): + pass + + # Exit a parse tree produced by SqlBaseParser#sampleByRows. + def exitSampleByRows(self, ctx:SqlBaseParser.SampleByRowsContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#sampleByBucket. + def enterSampleByBucket(self, ctx:SqlBaseParser.SampleByBucketContext): + pass + + # Exit a parse tree produced by SqlBaseParser#sampleByBucket. + def exitSampleByBucket(self, ctx:SqlBaseParser.SampleByBucketContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#sampleByBytes. + def enterSampleByBytes(self, ctx:SqlBaseParser.SampleByBytesContext): + pass + + # Exit a parse tree produced by SqlBaseParser#sampleByBytes. + def exitSampleByBytes(self, ctx:SqlBaseParser.SampleByBytesContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#identifierList. + def enterIdentifierList(self, ctx:SqlBaseParser.IdentifierListContext): + pass + + # Exit a parse tree produced by SqlBaseParser#identifierList. + def exitIdentifierList(self, ctx:SqlBaseParser.IdentifierListContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#identifierSeq. + def enterIdentifierSeq(self, ctx:SqlBaseParser.IdentifierSeqContext): + pass + + # Exit a parse tree produced by SqlBaseParser#identifierSeq. + def exitIdentifierSeq(self, ctx:SqlBaseParser.IdentifierSeqContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#orderedIdentifierList. + def enterOrderedIdentifierList(self, ctx:SqlBaseParser.OrderedIdentifierListContext): + pass + + # Exit a parse tree produced by SqlBaseParser#orderedIdentifierList. + def exitOrderedIdentifierList(self, ctx:SqlBaseParser.OrderedIdentifierListContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#orderedIdentifier. + def enterOrderedIdentifier(self, ctx:SqlBaseParser.OrderedIdentifierContext): + pass + + # Exit a parse tree produced by SqlBaseParser#orderedIdentifier. + def exitOrderedIdentifier(self, ctx:SqlBaseParser.OrderedIdentifierContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#identifierCommentList. + def enterIdentifierCommentList(self, ctx:SqlBaseParser.IdentifierCommentListContext): + pass + + # Exit a parse tree produced by SqlBaseParser#identifierCommentList. + def exitIdentifierCommentList(self, ctx:SqlBaseParser.IdentifierCommentListContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#identifierComment. + def enterIdentifierComment(self, ctx:SqlBaseParser.IdentifierCommentContext): + pass + + # Exit a parse tree produced by SqlBaseParser#identifierComment. + def exitIdentifierComment(self, ctx:SqlBaseParser.IdentifierCommentContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#tableName. + def enterTableName(self, ctx:SqlBaseParser.TableNameContext): + pass + + # Exit a parse tree produced by SqlBaseParser#tableName. + def exitTableName(self, ctx:SqlBaseParser.TableNameContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#aliasedQuery. + def enterAliasedQuery(self, ctx:SqlBaseParser.AliasedQueryContext): + pass + + # Exit a parse tree produced by SqlBaseParser#aliasedQuery. + def exitAliasedQuery(self, ctx:SqlBaseParser.AliasedQueryContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#aliasedRelation. + def enterAliasedRelation(self, ctx:SqlBaseParser.AliasedRelationContext): + pass + + # Exit a parse tree produced by SqlBaseParser#aliasedRelation. + def exitAliasedRelation(self, ctx:SqlBaseParser.AliasedRelationContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#inlineTableDefault2. + def enterInlineTableDefault2(self, ctx:SqlBaseParser.InlineTableDefault2Context): + pass + + # Exit a parse tree produced by SqlBaseParser#inlineTableDefault2. + def exitInlineTableDefault2(self, ctx:SqlBaseParser.InlineTableDefault2Context): + pass + + + # Enter a parse tree produced by SqlBaseParser#tableValuedFunction. + def enterTableValuedFunction(self, ctx:SqlBaseParser.TableValuedFunctionContext): + pass + + # Exit a parse tree produced by SqlBaseParser#tableValuedFunction. + def exitTableValuedFunction(self, ctx:SqlBaseParser.TableValuedFunctionContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#inlineTable. + def enterInlineTable(self, ctx:SqlBaseParser.InlineTableContext): + pass + + # Exit a parse tree produced by SqlBaseParser#inlineTable. + def exitInlineTable(self, ctx:SqlBaseParser.InlineTableContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#functionTable. + def enterFunctionTable(self, ctx:SqlBaseParser.FunctionTableContext): + pass + + # Exit a parse tree produced by SqlBaseParser#functionTable. + def exitFunctionTable(self, ctx:SqlBaseParser.FunctionTableContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#tableAlias. + def enterTableAlias(self, ctx:SqlBaseParser.TableAliasContext): + pass + + # Exit a parse tree produced by SqlBaseParser#tableAlias. + def exitTableAlias(self, ctx:SqlBaseParser.TableAliasContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#rowFormatSerde. + def enterRowFormatSerde(self, ctx:SqlBaseParser.RowFormatSerdeContext): + pass + + # Exit a parse tree produced by SqlBaseParser#rowFormatSerde. + def exitRowFormatSerde(self, ctx:SqlBaseParser.RowFormatSerdeContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#rowFormatDelimited. + def enterRowFormatDelimited(self, ctx:SqlBaseParser.RowFormatDelimitedContext): + pass + + # Exit a parse tree produced by SqlBaseParser#rowFormatDelimited. + def exitRowFormatDelimited(self, ctx:SqlBaseParser.RowFormatDelimitedContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#multipartIdentifierList. + def enterMultipartIdentifierList(self, ctx:SqlBaseParser.MultipartIdentifierListContext): + pass + + # Exit a parse tree produced by SqlBaseParser#multipartIdentifierList. + def exitMultipartIdentifierList(self, ctx:SqlBaseParser.MultipartIdentifierListContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#multipartIdentifier. + def enterMultipartIdentifier(self, ctx:SqlBaseParser.MultipartIdentifierContext): + pass + + # Exit a parse tree produced by SqlBaseParser#multipartIdentifier. + def exitMultipartIdentifier(self, ctx:SqlBaseParser.MultipartIdentifierContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#multipartIdentifierPropertyList. + def enterMultipartIdentifierPropertyList(self, ctx:SqlBaseParser.MultipartIdentifierPropertyListContext): + pass + + # Exit a parse tree produced by SqlBaseParser#multipartIdentifierPropertyList. + def exitMultipartIdentifierPropertyList(self, ctx:SqlBaseParser.MultipartIdentifierPropertyListContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#multipartIdentifierProperty. + def enterMultipartIdentifierProperty(self, ctx:SqlBaseParser.MultipartIdentifierPropertyContext): + pass + + # Exit a parse tree produced by SqlBaseParser#multipartIdentifierProperty. + def exitMultipartIdentifierProperty(self, ctx:SqlBaseParser.MultipartIdentifierPropertyContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#tableIdentifier. + def enterTableIdentifier(self, ctx:SqlBaseParser.TableIdentifierContext): + pass + + # Exit a parse tree produced by SqlBaseParser#tableIdentifier. + def exitTableIdentifier(self, ctx:SqlBaseParser.TableIdentifierContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#functionIdentifier. + def enterFunctionIdentifier(self, ctx:SqlBaseParser.FunctionIdentifierContext): + pass + + # Exit a parse tree produced by SqlBaseParser#functionIdentifier. + def exitFunctionIdentifier(self, ctx:SqlBaseParser.FunctionIdentifierContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#namedExpression. + def enterNamedExpression(self, ctx:SqlBaseParser.NamedExpressionContext): + pass + + # Exit a parse tree produced by SqlBaseParser#namedExpression. + def exitNamedExpression(self, ctx:SqlBaseParser.NamedExpressionContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#namedExpressionSeq. + def enterNamedExpressionSeq(self, ctx:SqlBaseParser.NamedExpressionSeqContext): + pass + + # Exit a parse tree produced by SqlBaseParser#namedExpressionSeq. + def exitNamedExpressionSeq(self, ctx:SqlBaseParser.NamedExpressionSeqContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#partitionFieldList. + def enterPartitionFieldList(self, ctx:SqlBaseParser.PartitionFieldListContext): + pass + + # Exit a parse tree produced by SqlBaseParser#partitionFieldList. + def exitPartitionFieldList(self, ctx:SqlBaseParser.PartitionFieldListContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#partitionTransform. + def enterPartitionTransform(self, ctx:SqlBaseParser.PartitionTransformContext): + pass + + # Exit a parse tree produced by SqlBaseParser#partitionTransform. + def exitPartitionTransform(self, ctx:SqlBaseParser.PartitionTransformContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#partitionColumn. + def enterPartitionColumn(self, ctx:SqlBaseParser.PartitionColumnContext): + pass + + # Exit a parse tree produced by SqlBaseParser#partitionColumn. + def exitPartitionColumn(self, ctx:SqlBaseParser.PartitionColumnContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#identityTransform. + def enterIdentityTransform(self, ctx:SqlBaseParser.IdentityTransformContext): + pass + + # Exit a parse tree produced by SqlBaseParser#identityTransform. + def exitIdentityTransform(self, ctx:SqlBaseParser.IdentityTransformContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#applyTransform. + def enterApplyTransform(self, ctx:SqlBaseParser.ApplyTransformContext): + pass + + # Exit a parse tree produced by SqlBaseParser#applyTransform. + def exitApplyTransform(self, ctx:SqlBaseParser.ApplyTransformContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#transformArgument. + def enterTransformArgument(self, ctx:SqlBaseParser.TransformArgumentContext): + pass + + # Exit a parse tree produced by SqlBaseParser#transformArgument. + def exitTransformArgument(self, ctx:SqlBaseParser.TransformArgumentContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#expression. + def enterExpression(self, ctx:SqlBaseParser.ExpressionContext): + pass + + # Exit a parse tree produced by SqlBaseParser#expression. + def exitExpression(self, ctx:SqlBaseParser.ExpressionContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#expressionSeq. + def enterExpressionSeq(self, ctx:SqlBaseParser.ExpressionSeqContext): + pass + + # Exit a parse tree produced by SqlBaseParser#expressionSeq. + def exitExpressionSeq(self, ctx:SqlBaseParser.ExpressionSeqContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#logicalNot. + def enterLogicalNot(self, ctx:SqlBaseParser.LogicalNotContext): + pass + + # Exit a parse tree produced by SqlBaseParser#logicalNot. + def exitLogicalNot(self, ctx:SqlBaseParser.LogicalNotContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#predicated. + def enterPredicated(self, ctx:SqlBaseParser.PredicatedContext): + pass + + # Exit a parse tree produced by SqlBaseParser#predicated. + def exitPredicated(self, ctx:SqlBaseParser.PredicatedContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#exists. + def enterExists(self, ctx:SqlBaseParser.ExistsContext): + pass + + # Exit a parse tree produced by SqlBaseParser#exists. + def exitExists(self, ctx:SqlBaseParser.ExistsContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#logicalBinary. + def enterLogicalBinary(self, ctx:SqlBaseParser.LogicalBinaryContext): + pass + + # Exit a parse tree produced by SqlBaseParser#logicalBinary. + def exitLogicalBinary(self, ctx:SqlBaseParser.LogicalBinaryContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#predicate. + def enterPredicate(self, ctx:SqlBaseParser.PredicateContext): + pass + + # Exit a parse tree produced by SqlBaseParser#predicate. + def exitPredicate(self, ctx:SqlBaseParser.PredicateContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#valueExpressionDefault. + def enterValueExpressionDefault(self, ctx:SqlBaseParser.ValueExpressionDefaultContext): + pass + + # Exit a parse tree produced by SqlBaseParser#valueExpressionDefault. + def exitValueExpressionDefault(self, ctx:SqlBaseParser.ValueExpressionDefaultContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#comparison. + def enterComparison(self, ctx:SqlBaseParser.ComparisonContext): + pass + + # Exit a parse tree produced by SqlBaseParser#comparison. + def exitComparison(self, ctx:SqlBaseParser.ComparisonContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#arithmeticBinary. + def enterArithmeticBinary(self, ctx:SqlBaseParser.ArithmeticBinaryContext): + pass + + # Exit a parse tree produced by SqlBaseParser#arithmeticBinary. + def exitArithmeticBinary(self, ctx:SqlBaseParser.ArithmeticBinaryContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#arithmeticUnary. + def enterArithmeticUnary(self, ctx:SqlBaseParser.ArithmeticUnaryContext): + pass + + # Exit a parse tree produced by SqlBaseParser#arithmeticUnary. + def exitArithmeticUnary(self, ctx:SqlBaseParser.ArithmeticUnaryContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#datetimeUnit. + def enterDatetimeUnit(self, ctx:SqlBaseParser.DatetimeUnitContext): + pass + + # Exit a parse tree produced by SqlBaseParser#datetimeUnit. + def exitDatetimeUnit(self, ctx:SqlBaseParser.DatetimeUnitContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#struct. + def enterStruct(self, ctx:SqlBaseParser.StructContext): + pass + + # Exit a parse tree produced by SqlBaseParser#struct. + def exitStruct(self, ctx:SqlBaseParser.StructContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#dereference. + def enterDereference(self, ctx:SqlBaseParser.DereferenceContext): + pass + + # Exit a parse tree produced by SqlBaseParser#dereference. + def exitDereference(self, ctx:SqlBaseParser.DereferenceContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#timestampadd. + def enterTimestampadd(self, ctx:SqlBaseParser.TimestampaddContext): + pass + + # Exit a parse tree produced by SqlBaseParser#timestampadd. + def exitTimestampadd(self, ctx:SqlBaseParser.TimestampaddContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#substring. + def enterSubstring(self, ctx:SqlBaseParser.SubstringContext): + pass + + # Exit a parse tree produced by SqlBaseParser#substring. + def exitSubstring(self, ctx:SqlBaseParser.SubstringContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#cast. + def enterCast(self, ctx:SqlBaseParser.CastContext): + pass + + # Exit a parse tree produced by SqlBaseParser#cast. + def exitCast(self, ctx:SqlBaseParser.CastContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#lambda. + def enterLambda(self, ctx:SqlBaseParser.LambdaContext): + pass + + # Exit a parse tree produced by SqlBaseParser#lambda. + def exitLambda(self, ctx:SqlBaseParser.LambdaContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#parenthesizedExpression. + def enterParenthesizedExpression(self, ctx:SqlBaseParser.ParenthesizedExpressionContext): + pass + + # Exit a parse tree produced by SqlBaseParser#parenthesizedExpression. + def exitParenthesizedExpression(self, ctx:SqlBaseParser.ParenthesizedExpressionContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#any_value. + def enterAny_value(self, ctx:SqlBaseParser.Any_valueContext): + pass + + # Exit a parse tree produced by SqlBaseParser#any_value. + def exitAny_value(self, ctx:SqlBaseParser.Any_valueContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#trim. + def enterTrim(self, ctx:SqlBaseParser.TrimContext): + pass + + # Exit a parse tree produced by SqlBaseParser#trim. + def exitTrim(self, ctx:SqlBaseParser.TrimContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#simpleCase. + def enterSimpleCase(self, ctx:SqlBaseParser.SimpleCaseContext): + pass + + # Exit a parse tree produced by SqlBaseParser#simpleCase. + def exitSimpleCase(self, ctx:SqlBaseParser.SimpleCaseContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#currentLike. + def enterCurrentLike(self, ctx:SqlBaseParser.CurrentLikeContext): + pass + + # Exit a parse tree produced by SqlBaseParser#currentLike. + def exitCurrentLike(self, ctx:SqlBaseParser.CurrentLikeContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#columnReference. + def enterColumnReference(self, ctx:SqlBaseParser.ColumnReferenceContext): + pass + + # Exit a parse tree produced by SqlBaseParser#columnReference. + def exitColumnReference(self, ctx:SqlBaseParser.ColumnReferenceContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#rowConstructor. + def enterRowConstructor(self, ctx:SqlBaseParser.RowConstructorContext): + pass + + # Exit a parse tree produced by SqlBaseParser#rowConstructor. + def exitRowConstructor(self, ctx:SqlBaseParser.RowConstructorContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#last. + def enterLast(self, ctx:SqlBaseParser.LastContext): + pass + + # Exit a parse tree produced by SqlBaseParser#last. + def exitLast(self, ctx:SqlBaseParser.LastContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#star. + def enterStar(self, ctx:SqlBaseParser.StarContext): + pass + + # Exit a parse tree produced by SqlBaseParser#star. + def exitStar(self, ctx:SqlBaseParser.StarContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#overlay. + def enterOverlay(self, ctx:SqlBaseParser.OverlayContext): + pass + + # Exit a parse tree produced by SqlBaseParser#overlay. + def exitOverlay(self, ctx:SqlBaseParser.OverlayContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#subscript. + def enterSubscript(self, ctx:SqlBaseParser.SubscriptContext): + pass + + # Exit a parse tree produced by SqlBaseParser#subscript. + def exitSubscript(self, ctx:SqlBaseParser.SubscriptContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#timestampdiff. + def enterTimestampdiff(self, ctx:SqlBaseParser.TimestampdiffContext): + pass + + # Exit a parse tree produced by SqlBaseParser#timestampdiff. + def exitTimestampdiff(self, ctx:SqlBaseParser.TimestampdiffContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#subqueryExpression. + def enterSubqueryExpression(self, ctx:SqlBaseParser.SubqueryExpressionContext): + pass + + # Exit a parse tree produced by SqlBaseParser#subqueryExpression. + def exitSubqueryExpression(self, ctx:SqlBaseParser.SubqueryExpressionContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#constantDefault. + def enterConstantDefault(self, ctx:SqlBaseParser.ConstantDefaultContext): + pass + + # Exit a parse tree produced by SqlBaseParser#constantDefault. + def exitConstantDefault(self, ctx:SqlBaseParser.ConstantDefaultContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#extract. + def enterExtract(self, ctx:SqlBaseParser.ExtractContext): + pass + + # Exit a parse tree produced by SqlBaseParser#extract. + def exitExtract(self, ctx:SqlBaseParser.ExtractContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#percentile. + def enterPercentile(self, ctx:SqlBaseParser.PercentileContext): + pass + + # Exit a parse tree produced by SqlBaseParser#percentile. + def exitPercentile(self, ctx:SqlBaseParser.PercentileContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#functionCall. + def enterFunctionCall(self, ctx:SqlBaseParser.FunctionCallContext): + pass + + # Exit a parse tree produced by SqlBaseParser#functionCall. + def exitFunctionCall(self, ctx:SqlBaseParser.FunctionCallContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#searchedCase. + def enterSearchedCase(self, ctx:SqlBaseParser.SearchedCaseContext): + pass + + # Exit a parse tree produced by SqlBaseParser#searchedCase. + def exitSearchedCase(self, ctx:SqlBaseParser.SearchedCaseContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#position. + def enterPosition(self, ctx:SqlBaseParser.PositionContext): + pass + + # Exit a parse tree produced by SqlBaseParser#position. + def exitPosition(self, ctx:SqlBaseParser.PositionContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#first. + def enterFirst(self, ctx:SqlBaseParser.FirstContext): + pass + + # Exit a parse tree produced by SqlBaseParser#first. + def exitFirst(self, ctx:SqlBaseParser.FirstContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#nullLiteral. + def enterNullLiteral(self, ctx:SqlBaseParser.NullLiteralContext): + pass + + # Exit a parse tree produced by SqlBaseParser#nullLiteral. + def exitNullLiteral(self, ctx:SqlBaseParser.NullLiteralContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#parameterLiteral. + def enterParameterLiteral(self, ctx:SqlBaseParser.ParameterLiteralContext): + pass + + # Exit a parse tree produced by SqlBaseParser#parameterLiteral. + def exitParameterLiteral(self, ctx:SqlBaseParser.ParameterLiteralContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#intervalLiteral. + def enterIntervalLiteral(self, ctx:SqlBaseParser.IntervalLiteralContext): + pass + + # Exit a parse tree produced by SqlBaseParser#intervalLiteral. + def exitIntervalLiteral(self, ctx:SqlBaseParser.IntervalLiteralContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#typeConstructor. + def enterTypeConstructor(self, ctx:SqlBaseParser.TypeConstructorContext): + pass + + # Exit a parse tree produced by SqlBaseParser#typeConstructor. + def exitTypeConstructor(self, ctx:SqlBaseParser.TypeConstructorContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#numericLiteral. + def enterNumericLiteral(self, ctx:SqlBaseParser.NumericLiteralContext): + pass + + # Exit a parse tree produced by SqlBaseParser#numericLiteral. + def exitNumericLiteral(self, ctx:SqlBaseParser.NumericLiteralContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#booleanLiteral. + def enterBooleanLiteral(self, ctx:SqlBaseParser.BooleanLiteralContext): + pass + + # Exit a parse tree produced by SqlBaseParser#booleanLiteral. + def exitBooleanLiteral(self, ctx:SqlBaseParser.BooleanLiteralContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#stringLiteral. + def enterStringLiteral(self, ctx:SqlBaseParser.StringLiteralContext): + pass + + # Exit a parse tree produced by SqlBaseParser#stringLiteral. + def exitStringLiteral(self, ctx:SqlBaseParser.StringLiteralContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#comparisonOperator. + def enterComparisonOperator(self, ctx:SqlBaseParser.ComparisonOperatorContext): + pass + + # Exit a parse tree produced by SqlBaseParser#comparisonOperator. + def exitComparisonOperator(self, ctx:SqlBaseParser.ComparisonOperatorContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#arithmeticOperator. + def enterArithmeticOperator(self, ctx:SqlBaseParser.ArithmeticOperatorContext): + pass + + # Exit a parse tree produced by SqlBaseParser#arithmeticOperator. + def exitArithmeticOperator(self, ctx:SqlBaseParser.ArithmeticOperatorContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#predicateOperator. + def enterPredicateOperator(self, ctx:SqlBaseParser.PredicateOperatorContext): + pass + + # Exit a parse tree produced by SqlBaseParser#predicateOperator. + def exitPredicateOperator(self, ctx:SqlBaseParser.PredicateOperatorContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#booleanValue. + def enterBooleanValue(self, ctx:SqlBaseParser.BooleanValueContext): + pass + + # Exit a parse tree produced by SqlBaseParser#booleanValue. + def exitBooleanValue(self, ctx:SqlBaseParser.BooleanValueContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#interval. + def enterInterval(self, ctx:SqlBaseParser.IntervalContext): + pass + + # Exit a parse tree produced by SqlBaseParser#interval. + def exitInterval(self, ctx:SqlBaseParser.IntervalContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#errorCapturingMultiUnitsInterval. + def enterErrorCapturingMultiUnitsInterval(self, ctx:SqlBaseParser.ErrorCapturingMultiUnitsIntervalContext): + pass + + # Exit a parse tree produced by SqlBaseParser#errorCapturingMultiUnitsInterval. + def exitErrorCapturingMultiUnitsInterval(self, ctx:SqlBaseParser.ErrorCapturingMultiUnitsIntervalContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#multiUnitsInterval. + def enterMultiUnitsInterval(self, ctx:SqlBaseParser.MultiUnitsIntervalContext): + pass + + # Exit a parse tree produced by SqlBaseParser#multiUnitsInterval. + def exitMultiUnitsInterval(self, ctx:SqlBaseParser.MultiUnitsIntervalContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#errorCapturingUnitToUnitInterval. + def enterErrorCapturingUnitToUnitInterval(self, ctx:SqlBaseParser.ErrorCapturingUnitToUnitIntervalContext): + pass + + # Exit a parse tree produced by SqlBaseParser#errorCapturingUnitToUnitInterval. + def exitErrorCapturingUnitToUnitInterval(self, ctx:SqlBaseParser.ErrorCapturingUnitToUnitIntervalContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#unitToUnitInterval. + def enterUnitToUnitInterval(self, ctx:SqlBaseParser.UnitToUnitIntervalContext): + pass + + # Exit a parse tree produced by SqlBaseParser#unitToUnitInterval. + def exitUnitToUnitInterval(self, ctx:SqlBaseParser.UnitToUnitIntervalContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#intervalValue. + def enterIntervalValue(self, ctx:SqlBaseParser.IntervalValueContext): + pass + + # Exit a parse tree produced by SqlBaseParser#intervalValue. + def exitIntervalValue(self, ctx:SqlBaseParser.IntervalValueContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#unitInMultiUnits. + def enterUnitInMultiUnits(self, ctx:SqlBaseParser.UnitInMultiUnitsContext): + pass + + # Exit a parse tree produced by SqlBaseParser#unitInMultiUnits. + def exitUnitInMultiUnits(self, ctx:SqlBaseParser.UnitInMultiUnitsContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#unitInUnitToUnit. + def enterUnitInUnitToUnit(self, ctx:SqlBaseParser.UnitInUnitToUnitContext): + pass + + # Exit a parse tree produced by SqlBaseParser#unitInUnitToUnit. + def exitUnitInUnitToUnit(self, ctx:SqlBaseParser.UnitInUnitToUnitContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#colPosition. + def enterColPosition(self, ctx:SqlBaseParser.ColPositionContext): + pass + + # Exit a parse tree produced by SqlBaseParser#colPosition. + def exitColPosition(self, ctx:SqlBaseParser.ColPositionContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#complexDataType. + def enterComplexDataType(self, ctx:SqlBaseParser.ComplexDataTypeContext): + pass + + # Exit a parse tree produced by SqlBaseParser#complexDataType. + def exitComplexDataType(self, ctx:SqlBaseParser.ComplexDataTypeContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#yearMonthIntervalDataType. + def enterYearMonthIntervalDataType(self, ctx:SqlBaseParser.YearMonthIntervalDataTypeContext): + pass + + # Exit a parse tree produced by SqlBaseParser#yearMonthIntervalDataType. + def exitYearMonthIntervalDataType(self, ctx:SqlBaseParser.YearMonthIntervalDataTypeContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#dayTimeIntervalDataType. + def enterDayTimeIntervalDataType(self, ctx:SqlBaseParser.DayTimeIntervalDataTypeContext): + pass + + # Exit a parse tree produced by SqlBaseParser#dayTimeIntervalDataType. + def exitDayTimeIntervalDataType(self, ctx:SqlBaseParser.DayTimeIntervalDataTypeContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#primitiveDataType. + def enterPrimitiveDataType(self, ctx:SqlBaseParser.PrimitiveDataTypeContext): + pass + + # Exit a parse tree produced by SqlBaseParser#primitiveDataType. + def exitPrimitiveDataType(self, ctx:SqlBaseParser.PrimitiveDataTypeContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#qualifiedColTypeWithPositionList. + def enterQualifiedColTypeWithPositionList(self, ctx:SqlBaseParser.QualifiedColTypeWithPositionListContext): + pass + + # Exit a parse tree produced by SqlBaseParser#qualifiedColTypeWithPositionList. + def exitQualifiedColTypeWithPositionList(self, ctx:SqlBaseParser.QualifiedColTypeWithPositionListContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#qualifiedColTypeWithPosition. + def enterQualifiedColTypeWithPosition(self, ctx:SqlBaseParser.QualifiedColTypeWithPositionContext): + pass + + # Exit a parse tree produced by SqlBaseParser#qualifiedColTypeWithPosition. + def exitQualifiedColTypeWithPosition(self, ctx:SqlBaseParser.QualifiedColTypeWithPositionContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#colDefinitionDescriptorWithPosition. + def enterColDefinitionDescriptorWithPosition(self, ctx:SqlBaseParser.ColDefinitionDescriptorWithPositionContext): + pass + + # Exit a parse tree produced by SqlBaseParser#colDefinitionDescriptorWithPosition. + def exitColDefinitionDescriptorWithPosition(self, ctx:SqlBaseParser.ColDefinitionDescriptorWithPositionContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#defaultExpression. + def enterDefaultExpression(self, ctx:SqlBaseParser.DefaultExpressionContext): + pass + + # Exit a parse tree produced by SqlBaseParser#defaultExpression. + def exitDefaultExpression(self, ctx:SqlBaseParser.DefaultExpressionContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#colTypeList. + def enterColTypeList(self, ctx:SqlBaseParser.ColTypeListContext): + pass + + # Exit a parse tree produced by SqlBaseParser#colTypeList. + def exitColTypeList(self, ctx:SqlBaseParser.ColTypeListContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#colType. + def enterColType(self, ctx:SqlBaseParser.ColTypeContext): + pass + + # Exit a parse tree produced by SqlBaseParser#colType. + def exitColType(self, ctx:SqlBaseParser.ColTypeContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#createOrReplaceTableColTypeList. + def enterCreateOrReplaceTableColTypeList(self, ctx:SqlBaseParser.CreateOrReplaceTableColTypeListContext): + pass + + # Exit a parse tree produced by SqlBaseParser#createOrReplaceTableColTypeList. + def exitCreateOrReplaceTableColTypeList(self, ctx:SqlBaseParser.CreateOrReplaceTableColTypeListContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#createOrReplaceTableColType. + def enterCreateOrReplaceTableColType(self, ctx:SqlBaseParser.CreateOrReplaceTableColTypeContext): + pass + + # Exit a parse tree produced by SqlBaseParser#createOrReplaceTableColType. + def exitCreateOrReplaceTableColType(self, ctx:SqlBaseParser.CreateOrReplaceTableColTypeContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#colDefinitionOption. + def enterColDefinitionOption(self, ctx:SqlBaseParser.ColDefinitionOptionContext): + pass + + # Exit a parse tree produced by SqlBaseParser#colDefinitionOption. + def exitColDefinitionOption(self, ctx:SqlBaseParser.ColDefinitionOptionContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#generationExpression. + def enterGenerationExpression(self, ctx:SqlBaseParser.GenerationExpressionContext): + pass + + # Exit a parse tree produced by SqlBaseParser#generationExpression. + def exitGenerationExpression(self, ctx:SqlBaseParser.GenerationExpressionContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#complexColTypeList. + def enterComplexColTypeList(self, ctx:SqlBaseParser.ComplexColTypeListContext): + pass + + # Exit a parse tree produced by SqlBaseParser#complexColTypeList. + def exitComplexColTypeList(self, ctx:SqlBaseParser.ComplexColTypeListContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#complexColType. + def enterComplexColType(self, ctx:SqlBaseParser.ComplexColTypeContext): + pass + + # Exit a parse tree produced by SqlBaseParser#complexColType. + def exitComplexColType(self, ctx:SqlBaseParser.ComplexColTypeContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#whenClause. + def enterWhenClause(self, ctx:SqlBaseParser.WhenClauseContext): + pass + + # Exit a parse tree produced by SqlBaseParser#whenClause. + def exitWhenClause(self, ctx:SqlBaseParser.WhenClauseContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#windowClause. + def enterWindowClause(self, ctx:SqlBaseParser.WindowClauseContext): + pass + + # Exit a parse tree produced by SqlBaseParser#windowClause. + def exitWindowClause(self, ctx:SqlBaseParser.WindowClauseContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#namedWindow. + def enterNamedWindow(self, ctx:SqlBaseParser.NamedWindowContext): + pass + + # Exit a parse tree produced by SqlBaseParser#namedWindow. + def exitNamedWindow(self, ctx:SqlBaseParser.NamedWindowContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#windowRef. + def enterWindowRef(self, ctx:SqlBaseParser.WindowRefContext): + pass + + # Exit a parse tree produced by SqlBaseParser#windowRef. + def exitWindowRef(self, ctx:SqlBaseParser.WindowRefContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#windowDef. + def enterWindowDef(self, ctx:SqlBaseParser.WindowDefContext): + pass + + # Exit a parse tree produced by SqlBaseParser#windowDef. + def exitWindowDef(self, ctx:SqlBaseParser.WindowDefContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#windowFrame. + def enterWindowFrame(self, ctx:SqlBaseParser.WindowFrameContext): + pass + + # Exit a parse tree produced by SqlBaseParser#windowFrame. + def exitWindowFrame(self, ctx:SqlBaseParser.WindowFrameContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#frameBound. + def enterFrameBound(self, ctx:SqlBaseParser.FrameBoundContext): + pass + + # Exit a parse tree produced by SqlBaseParser#frameBound. + def exitFrameBound(self, ctx:SqlBaseParser.FrameBoundContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#qualifiedNameList. + def enterQualifiedNameList(self, ctx:SqlBaseParser.QualifiedNameListContext): + pass + + # Exit a parse tree produced by SqlBaseParser#qualifiedNameList. + def exitQualifiedNameList(self, ctx:SqlBaseParser.QualifiedNameListContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#functionName. + def enterFunctionName(self, ctx:SqlBaseParser.FunctionNameContext): + pass + + # Exit a parse tree produced by SqlBaseParser#functionName. + def exitFunctionName(self, ctx:SqlBaseParser.FunctionNameContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#qualifiedName. + def enterQualifiedName(self, ctx:SqlBaseParser.QualifiedNameContext): + pass + + # Exit a parse tree produced by SqlBaseParser#qualifiedName. + def exitQualifiedName(self, ctx:SqlBaseParser.QualifiedNameContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#errorCapturingIdentifier. + def enterErrorCapturingIdentifier(self, ctx:SqlBaseParser.ErrorCapturingIdentifierContext): + pass + + # Exit a parse tree produced by SqlBaseParser#errorCapturingIdentifier. + def exitErrorCapturingIdentifier(self, ctx:SqlBaseParser.ErrorCapturingIdentifierContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#errorIdent. + def enterErrorIdent(self, ctx:SqlBaseParser.ErrorIdentContext): + pass + + # Exit a parse tree produced by SqlBaseParser#errorIdent. + def exitErrorIdent(self, ctx:SqlBaseParser.ErrorIdentContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#realIdent. + def enterRealIdent(self, ctx:SqlBaseParser.RealIdentContext): + pass + + # Exit a parse tree produced by SqlBaseParser#realIdent. + def exitRealIdent(self, ctx:SqlBaseParser.RealIdentContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#identifier. + def enterIdentifier(self, ctx:SqlBaseParser.IdentifierContext): + pass + + # Exit a parse tree produced by SqlBaseParser#identifier. + def exitIdentifier(self, ctx:SqlBaseParser.IdentifierContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#unquotedIdentifier. + def enterUnquotedIdentifier(self, ctx:SqlBaseParser.UnquotedIdentifierContext): + pass + + # Exit a parse tree produced by SqlBaseParser#unquotedIdentifier. + def exitUnquotedIdentifier(self, ctx:SqlBaseParser.UnquotedIdentifierContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#quotedIdentifierAlternative. + def enterQuotedIdentifierAlternative(self, ctx:SqlBaseParser.QuotedIdentifierAlternativeContext): + pass + + # Exit a parse tree produced by SqlBaseParser#quotedIdentifierAlternative. + def exitQuotedIdentifierAlternative(self, ctx:SqlBaseParser.QuotedIdentifierAlternativeContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#quotedIdentifier. + def enterQuotedIdentifier(self, ctx:SqlBaseParser.QuotedIdentifierContext): + pass + + # Exit a parse tree produced by SqlBaseParser#quotedIdentifier. + def exitQuotedIdentifier(self, ctx:SqlBaseParser.QuotedIdentifierContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#backQuotedIdentifier. + def enterBackQuotedIdentifier(self, ctx:SqlBaseParser.BackQuotedIdentifierContext): + pass + + # Exit a parse tree produced by SqlBaseParser#backQuotedIdentifier. + def exitBackQuotedIdentifier(self, ctx:SqlBaseParser.BackQuotedIdentifierContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#exponentLiteral. + def enterExponentLiteral(self, ctx:SqlBaseParser.ExponentLiteralContext): + pass + + # Exit a parse tree produced by SqlBaseParser#exponentLiteral. + def exitExponentLiteral(self, ctx:SqlBaseParser.ExponentLiteralContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#decimalLiteral. + def enterDecimalLiteral(self, ctx:SqlBaseParser.DecimalLiteralContext): + pass + + # Exit a parse tree produced by SqlBaseParser#decimalLiteral. + def exitDecimalLiteral(self, ctx:SqlBaseParser.DecimalLiteralContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#legacyDecimalLiteral. + def enterLegacyDecimalLiteral(self, ctx:SqlBaseParser.LegacyDecimalLiteralContext): + pass + + # Exit a parse tree produced by SqlBaseParser#legacyDecimalLiteral. + def exitLegacyDecimalLiteral(self, ctx:SqlBaseParser.LegacyDecimalLiteralContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#integerLiteral. + def enterIntegerLiteral(self, ctx:SqlBaseParser.IntegerLiteralContext): + pass + + # Exit a parse tree produced by SqlBaseParser#integerLiteral. + def exitIntegerLiteral(self, ctx:SqlBaseParser.IntegerLiteralContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#bigIntLiteral. + def enterBigIntLiteral(self, ctx:SqlBaseParser.BigIntLiteralContext): + pass + + # Exit a parse tree produced by SqlBaseParser#bigIntLiteral. + def exitBigIntLiteral(self, ctx:SqlBaseParser.BigIntLiteralContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#smallIntLiteral. + def enterSmallIntLiteral(self, ctx:SqlBaseParser.SmallIntLiteralContext): + pass + + # Exit a parse tree produced by SqlBaseParser#smallIntLiteral. + def exitSmallIntLiteral(self, ctx:SqlBaseParser.SmallIntLiteralContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#tinyIntLiteral. + def enterTinyIntLiteral(self, ctx:SqlBaseParser.TinyIntLiteralContext): + pass + + # Exit a parse tree produced by SqlBaseParser#tinyIntLiteral. + def exitTinyIntLiteral(self, ctx:SqlBaseParser.TinyIntLiteralContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#doubleLiteral. + def enterDoubleLiteral(self, ctx:SqlBaseParser.DoubleLiteralContext): + pass + + # Exit a parse tree produced by SqlBaseParser#doubleLiteral. + def exitDoubleLiteral(self, ctx:SqlBaseParser.DoubleLiteralContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#floatLiteral. + def enterFloatLiteral(self, ctx:SqlBaseParser.FloatLiteralContext): + pass + + # Exit a parse tree produced by SqlBaseParser#floatLiteral. + def exitFloatLiteral(self, ctx:SqlBaseParser.FloatLiteralContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#bigDecimalLiteral. + def enterBigDecimalLiteral(self, ctx:SqlBaseParser.BigDecimalLiteralContext): + pass + + # Exit a parse tree produced by SqlBaseParser#bigDecimalLiteral. + def exitBigDecimalLiteral(self, ctx:SqlBaseParser.BigDecimalLiteralContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#alterColumnAction. + def enterAlterColumnAction(self, ctx:SqlBaseParser.AlterColumnActionContext): + pass + + # Exit a parse tree produced by SqlBaseParser#alterColumnAction. + def exitAlterColumnAction(self, ctx:SqlBaseParser.AlterColumnActionContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#stringLit. + def enterStringLit(self, ctx:SqlBaseParser.StringLitContext): + pass + + # Exit a parse tree produced by SqlBaseParser#stringLit. + def exitStringLit(self, ctx:SqlBaseParser.StringLitContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#comment. + def enterComment(self, ctx:SqlBaseParser.CommentContext): + pass + + # Exit a parse tree produced by SqlBaseParser#comment. + def exitComment(self, ctx:SqlBaseParser.CommentContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#version. + def enterVersion(self, ctx:SqlBaseParser.VersionContext): + pass + + # Exit a parse tree produced by SqlBaseParser#version. + def exitVersion(self, ctx:SqlBaseParser.VersionContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#ansiNonReserved. + def enterAnsiNonReserved(self, ctx:SqlBaseParser.AnsiNonReservedContext): + pass + + # Exit a parse tree produced by SqlBaseParser#ansiNonReserved. + def exitAnsiNonReserved(self, ctx:SqlBaseParser.AnsiNonReservedContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#strictNonReserved. + def enterStrictNonReserved(self, ctx:SqlBaseParser.StrictNonReservedContext): + pass + + # Exit a parse tree produced by SqlBaseParser#strictNonReserved. + def exitStrictNonReserved(self, ctx:SqlBaseParser.StrictNonReservedContext): + pass + + + # Enter a parse tree produced by SqlBaseParser#nonReserved. + def enterNonReserved(self, ctx:SqlBaseParser.NonReservedContext): + pass + + # Exit a parse tree produced by SqlBaseParser#nonReserved. + def exitNonReserved(self, ctx:SqlBaseParser.NonReservedContext): + pass + + + +del SqlBaseParser \ No newline at end of file diff --git a/datajunction-server/datajunction_server/sql/parsing/backends/grammar/generated/SqlBaseParserVisitor.py b/datajunction-server/datajunction_server/sql/parsing/backends/grammar/generated/SqlBaseParserVisitor.py new file mode 100644 index 000000000..c2ba957e2 --- /dev/null +++ b/datajunction-server/datajunction_server/sql/parsing/backends/grammar/generated/SqlBaseParserVisitor.py @@ -0,0 +1,1375 @@ +# Generated from SqlBaseParser.g4 by ANTLR 4.12.0 +from antlr4 import * + +if __name__ is not None and "." in __name__: + from .SqlBaseParser import SqlBaseParser +else: + from SqlBaseParser import SqlBaseParser + +# This class defines a complete generic visitor for a parse tree produced by SqlBaseParser. + + +class SqlBaseParserVisitor(ParseTreeVisitor): + + # Visit a parse tree produced by SqlBaseParser#singleStatement. + def visitSingleStatement(self, ctx: SqlBaseParser.SingleStatementContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#singleExpression. + def visitSingleExpression(self, ctx: SqlBaseParser.SingleExpressionContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#singleTableIdentifier. + def visitSingleTableIdentifier( + self, ctx: SqlBaseParser.SingleTableIdentifierContext, + ): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#singleMultipartIdentifier. + def visitSingleMultipartIdentifier( + self, ctx: SqlBaseParser.SingleMultipartIdentifierContext, + ): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#singleFunctionIdentifier. + def visitSingleFunctionIdentifier( + self, ctx: SqlBaseParser.SingleFunctionIdentifierContext, + ): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#singleDataType. + def visitSingleDataType(self, ctx: SqlBaseParser.SingleDataTypeContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#singleTableSchema. + def visitSingleTableSchema(self, ctx: SqlBaseParser.SingleTableSchemaContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#statementDefault. + def visitStatementDefault(self, ctx: SqlBaseParser.StatementDefaultContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#dmlStatement. + def visitDmlStatement(self, ctx: SqlBaseParser.DmlStatementContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#use. + def visitUse(self, ctx: SqlBaseParser.UseContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#useNamespace. + def visitUseNamespace(self, ctx: SqlBaseParser.UseNamespaceContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#setCatalog. + def visitSetCatalog(self, ctx: SqlBaseParser.SetCatalogContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#createNamespace. + def visitCreateNamespace(self, ctx: SqlBaseParser.CreateNamespaceContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#setNamespaceProperties. + def visitSetNamespaceProperties( + self, ctx: SqlBaseParser.SetNamespacePropertiesContext, + ): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#setNamespaceLocation. + def visitSetNamespaceLocation(self, ctx: SqlBaseParser.SetNamespaceLocationContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#dropNamespace. + def visitDropNamespace(self, ctx: SqlBaseParser.DropNamespaceContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#showNamespaces. + def visitShowNamespaces(self, ctx: SqlBaseParser.ShowNamespacesContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#createTable. + def visitCreateTable(self, ctx: SqlBaseParser.CreateTableContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#createTableLike. + def visitCreateTableLike(self, ctx: SqlBaseParser.CreateTableLikeContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#replaceTable. + def visitReplaceTable(self, ctx: SqlBaseParser.ReplaceTableContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#analyze. + def visitAnalyze(self, ctx: SqlBaseParser.AnalyzeContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#analyzeTables. + def visitAnalyzeTables(self, ctx: SqlBaseParser.AnalyzeTablesContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#addTableColumns. + def visitAddTableColumns(self, ctx: SqlBaseParser.AddTableColumnsContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#renameTableColumn. + def visitRenameTableColumn(self, ctx: SqlBaseParser.RenameTableColumnContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#dropTableColumns. + def visitDropTableColumns(self, ctx: SqlBaseParser.DropTableColumnsContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#renameTable. + def visitRenameTable(self, ctx: SqlBaseParser.RenameTableContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#setTableProperties. + def visitSetTableProperties(self, ctx: SqlBaseParser.SetTablePropertiesContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#unsetTableProperties. + def visitUnsetTableProperties(self, ctx: SqlBaseParser.UnsetTablePropertiesContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#alterTableAlterColumn. + def visitAlterTableAlterColumn( + self, ctx: SqlBaseParser.AlterTableAlterColumnContext, + ): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#hiveChangeColumn. + def visitHiveChangeColumn(self, ctx: SqlBaseParser.HiveChangeColumnContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#hiveReplaceColumns. + def visitHiveReplaceColumns(self, ctx: SqlBaseParser.HiveReplaceColumnsContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#setTableSerDe. + def visitSetTableSerDe(self, ctx: SqlBaseParser.SetTableSerDeContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#addTablePartition. + def visitAddTablePartition(self, ctx: SqlBaseParser.AddTablePartitionContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#renameTablePartition. + def visitRenameTablePartition(self, ctx: SqlBaseParser.RenameTablePartitionContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#dropTablePartitions. + def visitDropTablePartitions(self, ctx: SqlBaseParser.DropTablePartitionsContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#setTableLocation. + def visitSetTableLocation(self, ctx: SqlBaseParser.SetTableLocationContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#recoverPartitions. + def visitRecoverPartitions(self, ctx: SqlBaseParser.RecoverPartitionsContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#dropTable. + def visitDropTable(self, ctx: SqlBaseParser.DropTableContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#dropView. + def visitDropView(self, ctx: SqlBaseParser.DropViewContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#createView. + def visitCreateView(self, ctx: SqlBaseParser.CreateViewContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#createTempViewUsing. + def visitCreateTempViewUsing(self, ctx: SqlBaseParser.CreateTempViewUsingContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#alterViewQuery. + def visitAlterViewQuery(self, ctx: SqlBaseParser.AlterViewQueryContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#createFunction. + def visitCreateFunction(self, ctx: SqlBaseParser.CreateFunctionContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#dropFunction. + def visitDropFunction(self, ctx: SqlBaseParser.DropFunctionContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#explain. + def visitExplain(self, ctx: SqlBaseParser.ExplainContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#showTables. + def visitShowTables(self, ctx: SqlBaseParser.ShowTablesContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#showTableExtended. + def visitShowTableExtended(self, ctx: SqlBaseParser.ShowTableExtendedContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#showTblProperties. + def visitShowTblProperties(self, ctx: SqlBaseParser.ShowTblPropertiesContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#showColumns. + def visitShowColumns(self, ctx: SqlBaseParser.ShowColumnsContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#showViews. + def visitShowViews(self, ctx: SqlBaseParser.ShowViewsContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#showPartitions. + def visitShowPartitions(self, ctx: SqlBaseParser.ShowPartitionsContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#showFunctions. + def visitShowFunctions(self, ctx: SqlBaseParser.ShowFunctionsContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#showCreateTable. + def visitShowCreateTable(self, ctx: SqlBaseParser.ShowCreateTableContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#showCurrentNamespace. + def visitShowCurrentNamespace(self, ctx: SqlBaseParser.ShowCurrentNamespaceContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#showCatalogs. + def visitShowCatalogs(self, ctx: SqlBaseParser.ShowCatalogsContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#describeFunction. + def visitDescribeFunction(self, ctx: SqlBaseParser.DescribeFunctionContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#describeNamespace. + def visitDescribeNamespace(self, ctx: SqlBaseParser.DescribeNamespaceContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#describeRelation. + def visitDescribeRelation(self, ctx: SqlBaseParser.DescribeRelationContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#describeQuery. + def visitDescribeQuery(self, ctx: SqlBaseParser.DescribeQueryContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#commentNamespace. + def visitCommentNamespace(self, ctx: SqlBaseParser.CommentNamespaceContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#commentTable. + def visitCommentTable(self, ctx: SqlBaseParser.CommentTableContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#refreshTable. + def visitRefreshTable(self, ctx: SqlBaseParser.RefreshTableContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#refreshFunction. + def visitRefreshFunction(self, ctx: SqlBaseParser.RefreshFunctionContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#refreshResource. + def visitRefreshResource(self, ctx: SqlBaseParser.RefreshResourceContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#cacheTable. + def visitCacheTable(self, ctx: SqlBaseParser.CacheTableContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#uncacheTable. + def visitUncacheTable(self, ctx: SqlBaseParser.UncacheTableContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#clearCache. + def visitClearCache(self, ctx: SqlBaseParser.ClearCacheContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#loadData. + def visitLoadData(self, ctx: SqlBaseParser.LoadDataContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#truncateTable. + def visitTruncateTable(self, ctx: SqlBaseParser.TruncateTableContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#repairTable. + def visitRepairTable(self, ctx: SqlBaseParser.RepairTableContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#manageResource. + def visitManageResource(self, ctx: SqlBaseParser.ManageResourceContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#failNativeCommand. + def visitFailNativeCommand(self, ctx: SqlBaseParser.FailNativeCommandContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#setTimeZone. + def visitSetTimeZone(self, ctx: SqlBaseParser.SetTimeZoneContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#setQuotedConfiguration. + def visitSetQuotedConfiguration( + self, ctx: SqlBaseParser.SetQuotedConfigurationContext, + ): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#setConfiguration. + def visitSetConfiguration(self, ctx: SqlBaseParser.SetConfigurationContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#resetQuotedConfiguration. + def visitResetQuotedConfiguration( + self, ctx: SqlBaseParser.ResetQuotedConfigurationContext, + ): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#resetConfiguration. + def visitResetConfiguration(self, ctx: SqlBaseParser.ResetConfigurationContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#createIndex. + def visitCreateIndex(self, ctx: SqlBaseParser.CreateIndexContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#dropIndex. + def visitDropIndex(self, ctx: SqlBaseParser.DropIndexContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#timezone. + def visitTimezone(self, ctx: SqlBaseParser.TimezoneContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#configKey. + def visitConfigKey(self, ctx: SqlBaseParser.ConfigKeyContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#configValue. + def visitConfigValue(self, ctx: SqlBaseParser.ConfigValueContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#unsupportedHiveNativeCommands. + def visitUnsupportedHiveNativeCommands( + self, ctx: SqlBaseParser.UnsupportedHiveNativeCommandsContext, + ): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#createTableHeader. + def visitCreateTableHeader(self, ctx: SqlBaseParser.CreateTableHeaderContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#replaceTableHeader. + def visitReplaceTableHeader(self, ctx: SqlBaseParser.ReplaceTableHeaderContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#bucketSpec. + def visitBucketSpec(self, ctx: SqlBaseParser.BucketSpecContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#skewSpec. + def visitSkewSpec(self, ctx: SqlBaseParser.SkewSpecContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#locationSpec. + def visitLocationSpec(self, ctx: SqlBaseParser.LocationSpecContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#commentSpec. + def visitCommentSpec(self, ctx: SqlBaseParser.CommentSpecContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#query. + def visitQuery(self, ctx: SqlBaseParser.QueryContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#insertOverwriteTable. + def visitInsertOverwriteTable(self, ctx: SqlBaseParser.InsertOverwriteTableContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#insertIntoTable. + def visitInsertIntoTable(self, ctx: SqlBaseParser.InsertIntoTableContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#insertIntoReplaceWhere. + def visitInsertIntoReplaceWhere( + self, ctx: SqlBaseParser.InsertIntoReplaceWhereContext, + ): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#insertOverwriteHiveDir. + def visitInsertOverwriteHiveDir( + self, ctx: SqlBaseParser.InsertOverwriteHiveDirContext, + ): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#insertOverwriteDir. + def visitInsertOverwriteDir(self, ctx: SqlBaseParser.InsertOverwriteDirContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#partitionSpecLocation. + def visitPartitionSpecLocation( + self, ctx: SqlBaseParser.PartitionSpecLocationContext, + ): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#partitionSpec. + def visitPartitionSpec(self, ctx: SqlBaseParser.PartitionSpecContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#partitionVal. + def visitPartitionVal(self, ctx: SqlBaseParser.PartitionValContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#namespace. + def visitNamespace(self, ctx: SqlBaseParser.NamespaceContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#namespaces. + def visitNamespaces(self, ctx: SqlBaseParser.NamespacesContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#describeFuncName. + def visitDescribeFuncName(self, ctx: SqlBaseParser.DescribeFuncNameContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#describeColName. + def visitDescribeColName(self, ctx: SqlBaseParser.DescribeColNameContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#ctes. + def visitCtes(self, ctx: SqlBaseParser.CtesContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#namedQuery. + def visitNamedQuery(self, ctx: SqlBaseParser.NamedQueryContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#tableProvider. + def visitTableProvider(self, ctx: SqlBaseParser.TableProviderContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#createTableClauses. + def visitCreateTableClauses(self, ctx: SqlBaseParser.CreateTableClausesContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#propertyList. + def visitPropertyList(self, ctx: SqlBaseParser.PropertyListContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#property. + def visitProperty(self, ctx: SqlBaseParser.PropertyContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#propertyKey. + def visitPropertyKey(self, ctx: SqlBaseParser.PropertyKeyContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#propertyValue. + def visitPropertyValue(self, ctx: SqlBaseParser.PropertyValueContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#constantList. + def visitConstantList(self, ctx: SqlBaseParser.ConstantListContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#nestedConstantList. + def visitNestedConstantList(self, ctx: SqlBaseParser.NestedConstantListContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#createFileFormat. + def visitCreateFileFormat(self, ctx: SqlBaseParser.CreateFileFormatContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#tableFileFormat. + def visitTableFileFormat(self, ctx: SqlBaseParser.TableFileFormatContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#genericFileFormat. + def visitGenericFileFormat(self, ctx: SqlBaseParser.GenericFileFormatContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#storageHandler. + def visitStorageHandler(self, ctx: SqlBaseParser.StorageHandlerContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#resource. + def visitResource(self, ctx: SqlBaseParser.ResourceContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#singleInsertQuery. + def visitSingleInsertQuery(self, ctx: SqlBaseParser.SingleInsertQueryContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#multiInsertQuery. + def visitMultiInsertQuery(self, ctx: SqlBaseParser.MultiInsertQueryContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#deleteFromTable. + def visitDeleteFromTable(self, ctx: SqlBaseParser.DeleteFromTableContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#updateTable. + def visitUpdateTable(self, ctx: SqlBaseParser.UpdateTableContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#mergeIntoTable. + def visitMergeIntoTable(self, ctx: SqlBaseParser.MergeIntoTableContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#queryOrganization. + def visitQueryOrganization(self, ctx: SqlBaseParser.QueryOrganizationContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#multiInsertQueryBody. + def visitMultiInsertQueryBody(self, ctx: SqlBaseParser.MultiInsertQueryBodyContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#queryTermDefault. + def visitQueryTermDefault(self, ctx: SqlBaseParser.QueryTermDefaultContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#setOperation. + def visitSetOperation(self, ctx: SqlBaseParser.SetOperationContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#queryPrimaryDefault. + def visitQueryPrimaryDefault(self, ctx: SqlBaseParser.QueryPrimaryDefaultContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#fromStmt. + def visitFromStmt(self, ctx: SqlBaseParser.FromStmtContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#table. + def visitTable(self, ctx: SqlBaseParser.TableContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#inlineTableDefault1. + def visitInlineTableDefault1(self, ctx: SqlBaseParser.InlineTableDefault1Context): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#subquery. + def visitSubquery(self, ctx: SqlBaseParser.SubqueryContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#sortItem. + def visitSortItem(self, ctx: SqlBaseParser.SortItemContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#fromStatement. + def visitFromStatement(self, ctx: SqlBaseParser.FromStatementContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#fromStatementBody. + def visitFromStatementBody(self, ctx: SqlBaseParser.FromStatementBodyContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#transformQuerySpecification. + def visitTransformQuerySpecification( + self, ctx: SqlBaseParser.TransformQuerySpecificationContext, + ): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#regularQuerySpecification. + def visitRegularQuerySpecification( + self, ctx: SqlBaseParser.RegularQuerySpecificationContext, + ): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#transformClause. + def visitTransformClause(self, ctx: SqlBaseParser.TransformClauseContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#selectClause. + def visitSelectClause(self, ctx: SqlBaseParser.SelectClauseContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#setClause. + def visitSetClause(self, ctx: SqlBaseParser.SetClauseContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#matchedClause. + def visitMatchedClause(self, ctx: SqlBaseParser.MatchedClauseContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#notMatchedClause. + def visitNotMatchedClause(self, ctx: SqlBaseParser.NotMatchedClauseContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#notMatchedBySourceClause. + def visitNotMatchedBySourceClause( + self, ctx: SqlBaseParser.NotMatchedBySourceClauseContext, + ): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#matchedAction. + def visitMatchedAction(self, ctx: SqlBaseParser.MatchedActionContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#notMatchedAction. + def visitNotMatchedAction(self, ctx: SqlBaseParser.NotMatchedActionContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#notMatchedBySourceAction. + def visitNotMatchedBySourceAction( + self, ctx: SqlBaseParser.NotMatchedBySourceActionContext, + ): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#assignmentList. + def visitAssignmentList(self, ctx: SqlBaseParser.AssignmentListContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#assignment. + def visitAssignment(self, ctx: SqlBaseParser.AssignmentContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#whereClause. + def visitWhereClause(self, ctx: SqlBaseParser.WhereClauseContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#havingClause. + def visitHavingClause(self, ctx: SqlBaseParser.HavingClauseContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#hint. + def visitHint(self, ctx: SqlBaseParser.HintContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#hintStatement. + def visitHintStatement(self, ctx: SqlBaseParser.HintStatementContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#fromClause. + def visitFromClause(self, ctx: SqlBaseParser.FromClauseContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#temporalClause. + def visitTemporalClause(self, ctx: SqlBaseParser.TemporalClauseContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#aggregationClause. + def visitAggregationClause(self, ctx: SqlBaseParser.AggregationClauseContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#groupByClause. + def visitGroupByClause(self, ctx: SqlBaseParser.GroupByClauseContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#groupingAnalytics. + def visitGroupingAnalytics(self, ctx: SqlBaseParser.GroupingAnalyticsContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#groupingElement. + def visitGroupingElement(self, ctx: SqlBaseParser.GroupingElementContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#groupingSet. + def visitGroupingSet(self, ctx: SqlBaseParser.GroupingSetContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#pivotClause. + def visitPivotClause(self, ctx: SqlBaseParser.PivotClauseContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#pivotColumn. + def visitPivotColumn(self, ctx: SqlBaseParser.PivotColumnContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#pivotValue. + def visitPivotValue(self, ctx: SqlBaseParser.PivotValueContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#unpivotClause. + def visitUnpivotClause(self, ctx: SqlBaseParser.UnpivotClauseContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#unpivotNullClause. + def visitUnpivotNullClause(self, ctx: SqlBaseParser.UnpivotNullClauseContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#unpivotOperator. + def visitUnpivotOperator(self, ctx: SqlBaseParser.UnpivotOperatorContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#unpivotSingleValueColumnClause. + def visitUnpivotSingleValueColumnClause( + self, ctx: SqlBaseParser.UnpivotSingleValueColumnClauseContext, + ): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#unpivotMultiValueColumnClause. + def visitUnpivotMultiValueColumnClause( + self, ctx: SqlBaseParser.UnpivotMultiValueColumnClauseContext, + ): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#unpivotColumnSet. + def visitUnpivotColumnSet(self, ctx: SqlBaseParser.UnpivotColumnSetContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#unpivotValueColumn. + def visitUnpivotValueColumn(self, ctx: SqlBaseParser.UnpivotValueColumnContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#unpivotNameColumn. + def visitUnpivotNameColumn(self, ctx: SqlBaseParser.UnpivotNameColumnContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#unpivotColumnAndAlias. + def visitUnpivotColumnAndAlias( + self, ctx: SqlBaseParser.UnpivotColumnAndAliasContext, + ): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#unpivotColumn. + def visitUnpivotColumn(self, ctx: SqlBaseParser.UnpivotColumnContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#unpivotAlias. + def visitUnpivotAlias(self, ctx: SqlBaseParser.UnpivotAliasContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#lateralView. + def visitLateralView(self, ctx: SqlBaseParser.LateralViewContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#setQuantifier. + def visitSetQuantifier(self, ctx: SqlBaseParser.SetQuantifierContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#relation. + def visitRelation(self, ctx: SqlBaseParser.RelationContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#relationExtension. + def visitRelationExtension(self, ctx: SqlBaseParser.RelationExtensionContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#joinRelation. + def visitJoinRelation(self, ctx: SqlBaseParser.JoinRelationContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#joinType. + def visitJoinType(self, ctx: SqlBaseParser.JoinTypeContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#joinCriteria. + def visitJoinCriteria(self, ctx: SqlBaseParser.JoinCriteriaContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#sample. + def visitSample(self, ctx: SqlBaseParser.SampleContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#sampleByPercentile. + def visitSampleByPercentile(self, ctx: SqlBaseParser.SampleByPercentileContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#sampleByRows. + def visitSampleByRows(self, ctx: SqlBaseParser.SampleByRowsContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#sampleByBucket. + def visitSampleByBucket(self, ctx: SqlBaseParser.SampleByBucketContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#sampleByBytes. + def visitSampleByBytes(self, ctx: SqlBaseParser.SampleByBytesContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#identifierList. + def visitIdentifierList(self, ctx: SqlBaseParser.IdentifierListContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#identifierSeq. + def visitIdentifierSeq(self, ctx: SqlBaseParser.IdentifierSeqContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#orderedIdentifierList. + def visitOrderedIdentifierList( + self, ctx: SqlBaseParser.OrderedIdentifierListContext, + ): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#orderedIdentifier. + def visitOrderedIdentifier(self, ctx: SqlBaseParser.OrderedIdentifierContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#identifierCommentList. + def visitIdentifierCommentList( + self, ctx: SqlBaseParser.IdentifierCommentListContext, + ): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#identifierComment. + def visitIdentifierComment(self, ctx: SqlBaseParser.IdentifierCommentContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#tableName. + def visitTableName(self, ctx: SqlBaseParser.TableNameContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#aliasedQuery. + def visitAliasedQuery(self, ctx: SqlBaseParser.AliasedQueryContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#aliasedRelation. + def visitAliasedRelation(self, ctx: SqlBaseParser.AliasedRelationContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#inlineTableDefault2. + def visitInlineTableDefault2(self, ctx: SqlBaseParser.InlineTableDefault2Context): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#tableValuedFunction. + def visitTableValuedFunction(self, ctx: SqlBaseParser.TableValuedFunctionContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#inlineTable. + def visitInlineTable(self, ctx: SqlBaseParser.InlineTableContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#functionTable. + def visitFunctionTable(self, ctx: SqlBaseParser.FunctionTableContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#tableAlias. + def visitTableAlias(self, ctx: SqlBaseParser.TableAliasContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#rowFormatSerde. + def visitRowFormatSerde(self, ctx: SqlBaseParser.RowFormatSerdeContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#rowFormatDelimited. + def visitRowFormatDelimited(self, ctx: SqlBaseParser.RowFormatDelimitedContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#multipartIdentifierList. + def visitMultipartIdentifierList( + self, ctx: SqlBaseParser.MultipartIdentifierListContext, + ): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#multipartIdentifier. + def visitMultipartIdentifier(self, ctx: SqlBaseParser.MultipartIdentifierContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#multipartIdentifierPropertyList. + def visitMultipartIdentifierPropertyList( + self, ctx: SqlBaseParser.MultipartIdentifierPropertyListContext, + ): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#multipartIdentifierProperty. + def visitMultipartIdentifierProperty( + self, ctx: SqlBaseParser.MultipartIdentifierPropertyContext, + ): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#tableIdentifier. + def visitTableIdentifier(self, ctx: SqlBaseParser.TableIdentifierContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#functionIdentifier. + def visitFunctionIdentifier(self, ctx: SqlBaseParser.FunctionIdentifierContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#namedExpression. + def visitNamedExpression(self, ctx: SqlBaseParser.NamedExpressionContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#namedExpressionSeq. + def visitNamedExpressionSeq(self, ctx: SqlBaseParser.NamedExpressionSeqContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#partitionFieldList. + def visitPartitionFieldList(self, ctx: SqlBaseParser.PartitionFieldListContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#partitionTransform. + def visitPartitionTransform(self, ctx: SqlBaseParser.PartitionTransformContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#partitionColumn. + def visitPartitionColumn(self, ctx: SqlBaseParser.PartitionColumnContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#identityTransform. + def visitIdentityTransform(self, ctx: SqlBaseParser.IdentityTransformContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#applyTransform. + def visitApplyTransform(self, ctx: SqlBaseParser.ApplyTransformContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#transformArgument. + def visitTransformArgument(self, ctx: SqlBaseParser.TransformArgumentContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#expression. + def visitExpression(self, ctx: SqlBaseParser.ExpressionContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#expressionSeq. + def visitExpressionSeq(self, ctx: SqlBaseParser.ExpressionSeqContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#logicalNot. + def visitLogicalNot(self, ctx: SqlBaseParser.LogicalNotContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#predicated. + def visitPredicated(self, ctx: SqlBaseParser.PredicatedContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#exists. + def visitExists(self, ctx: SqlBaseParser.ExistsContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#logicalBinary. + def visitLogicalBinary(self, ctx: SqlBaseParser.LogicalBinaryContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#predicate. + def visitPredicate(self, ctx: SqlBaseParser.PredicateContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#valueExpressionDefault. + def visitValueExpressionDefault( + self, ctx: SqlBaseParser.ValueExpressionDefaultContext, + ): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#comparison. + def visitComparison(self, ctx: SqlBaseParser.ComparisonContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#arithmeticBinary. + def visitArithmeticBinary(self, ctx: SqlBaseParser.ArithmeticBinaryContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#arithmeticUnary. + def visitArithmeticUnary(self, ctx: SqlBaseParser.ArithmeticUnaryContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#datetimeUnit. + def visitDatetimeUnit(self, ctx: SqlBaseParser.DatetimeUnitContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#struct. + def visitStruct(self, ctx: SqlBaseParser.StructContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#dereference. + def visitDereference(self, ctx: SqlBaseParser.DereferenceContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#timestampadd. + def visitTimestampadd(self, ctx: SqlBaseParser.TimestampaddContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#substring. + def visitSubstring(self, ctx: SqlBaseParser.SubstringContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#cast. + def visitCast(self, ctx: SqlBaseParser.CastContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#lambda. + def visitLambda(self, ctx: SqlBaseParser.LambdaContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#parenthesizedExpression. + def visitParenthesizedExpression( + self, ctx: SqlBaseParser.ParenthesizedExpressionContext, + ): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#any_value. + def visitAny_value(self, ctx: SqlBaseParser.Any_valueContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#trim. + def visitTrim(self, ctx: SqlBaseParser.TrimContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#simpleCase. + def visitSimpleCase(self, ctx: SqlBaseParser.SimpleCaseContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#currentLike. + def visitCurrentLike(self, ctx: SqlBaseParser.CurrentLikeContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#columnReference. + def visitColumnReference(self, ctx: SqlBaseParser.ColumnReferenceContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#rowConstructor. + def visitRowConstructor(self, ctx: SqlBaseParser.RowConstructorContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#last. + def visitLast(self, ctx: SqlBaseParser.LastContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#star. + def visitStar(self, ctx: SqlBaseParser.StarContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#overlay. + def visitOverlay(self, ctx: SqlBaseParser.OverlayContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#subscript. + def visitSubscript(self, ctx: SqlBaseParser.SubscriptContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#timestampdiff. + def visitTimestampdiff(self, ctx: SqlBaseParser.TimestampdiffContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#subqueryExpression. + def visitSubqueryExpression(self, ctx: SqlBaseParser.SubqueryExpressionContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#constantDefault. + def visitConstantDefault(self, ctx: SqlBaseParser.ConstantDefaultContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#extract. + def visitExtract(self, ctx: SqlBaseParser.ExtractContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#percentile. + def visitPercentile(self, ctx: SqlBaseParser.PercentileContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#functionCall. + def visitFunctionCall(self, ctx: SqlBaseParser.FunctionCallContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#searchedCase. + def visitSearchedCase(self, ctx: SqlBaseParser.SearchedCaseContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#position. + def visitPosition(self, ctx: SqlBaseParser.PositionContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#first. + def visitFirst(self, ctx: SqlBaseParser.FirstContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#nullLiteral. + def visitNullLiteral(self, ctx: SqlBaseParser.NullLiteralContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#parameterLiteral. + def visitParameterLiteral(self, ctx: SqlBaseParser.ParameterLiteralContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#intervalLiteral. + def visitIntervalLiteral(self, ctx: SqlBaseParser.IntervalLiteralContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#typeConstructor. + def visitTypeConstructor(self, ctx: SqlBaseParser.TypeConstructorContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#numericLiteral. + def visitNumericLiteral(self, ctx: SqlBaseParser.NumericLiteralContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#booleanLiteral. + def visitBooleanLiteral(self, ctx: SqlBaseParser.BooleanLiteralContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#stringLiteral. + def visitStringLiteral(self, ctx: SqlBaseParser.StringLiteralContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#comparisonOperator. + def visitComparisonOperator(self, ctx: SqlBaseParser.ComparisonOperatorContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#arithmeticOperator. + def visitArithmeticOperator(self, ctx: SqlBaseParser.ArithmeticOperatorContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#predicateOperator. + def visitPredicateOperator(self, ctx: SqlBaseParser.PredicateOperatorContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#booleanValue. + def visitBooleanValue(self, ctx: SqlBaseParser.BooleanValueContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#interval. + def visitInterval(self, ctx: SqlBaseParser.IntervalContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#errorCapturingMultiUnitsInterval. + def visitErrorCapturingMultiUnitsInterval( + self, ctx: SqlBaseParser.ErrorCapturingMultiUnitsIntervalContext, + ): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#multiUnitsInterval. + def visitMultiUnitsInterval(self, ctx: SqlBaseParser.MultiUnitsIntervalContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#errorCapturingUnitToUnitInterval. + def visitErrorCapturingUnitToUnitInterval( + self, ctx: SqlBaseParser.ErrorCapturingUnitToUnitIntervalContext, + ): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#unitToUnitInterval. + def visitUnitToUnitInterval(self, ctx: SqlBaseParser.UnitToUnitIntervalContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#intervalValue. + def visitIntervalValue(self, ctx: SqlBaseParser.IntervalValueContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#unitInMultiUnits. + def visitUnitInMultiUnits(self, ctx: SqlBaseParser.UnitInMultiUnitsContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#unitInUnitToUnit. + def visitUnitInUnitToUnit(self, ctx: SqlBaseParser.UnitInUnitToUnitContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#colPosition. + def visitColPosition(self, ctx: SqlBaseParser.ColPositionContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#complexDataType. + def visitComplexDataType(self, ctx: SqlBaseParser.ComplexDataTypeContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#yearMonthIntervalDataType. + def visitYearMonthIntervalDataType( + self, ctx: SqlBaseParser.YearMonthIntervalDataTypeContext, + ): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#dayTimeIntervalDataType. + def visitDayTimeIntervalDataType( + self, ctx: SqlBaseParser.DayTimeIntervalDataTypeContext, + ): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#primitiveDataType. + def visitPrimitiveDataType(self, ctx: SqlBaseParser.PrimitiveDataTypeContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#qualifiedColTypeWithPositionList. + def visitQualifiedColTypeWithPositionList( + self, ctx: SqlBaseParser.QualifiedColTypeWithPositionListContext, + ): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#qualifiedColTypeWithPosition. + def visitQualifiedColTypeWithPosition( + self, ctx: SqlBaseParser.QualifiedColTypeWithPositionContext, + ): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#colDefinitionDescriptorWithPosition. + def visitColDefinitionDescriptorWithPosition( + self, ctx: SqlBaseParser.ColDefinitionDescriptorWithPositionContext, + ): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#defaultExpression. + def visitDefaultExpression(self, ctx: SqlBaseParser.DefaultExpressionContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#colTypeList. + def visitColTypeList(self, ctx: SqlBaseParser.ColTypeListContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#colType. + def visitColType(self, ctx: SqlBaseParser.ColTypeContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#createOrReplaceTableColTypeList. + def visitCreateOrReplaceTableColTypeList( + self, ctx: SqlBaseParser.CreateOrReplaceTableColTypeListContext, + ): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#createOrReplaceTableColType. + def visitCreateOrReplaceTableColType( + self, ctx: SqlBaseParser.CreateOrReplaceTableColTypeContext, + ): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#colDefinitionOption. + def visitColDefinitionOption(self, ctx: SqlBaseParser.ColDefinitionOptionContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#generationExpression. + def visitGenerationExpression(self, ctx: SqlBaseParser.GenerationExpressionContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#complexColTypeList. + def visitComplexColTypeList(self, ctx: SqlBaseParser.ComplexColTypeListContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#complexColType. + def visitComplexColType(self, ctx: SqlBaseParser.ComplexColTypeContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#whenClause. + def visitWhenClause(self, ctx: SqlBaseParser.WhenClauseContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#windowClause. + def visitWindowClause(self, ctx: SqlBaseParser.WindowClauseContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#namedWindow. + def visitNamedWindow(self, ctx: SqlBaseParser.NamedWindowContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#windowRef. + def visitWindowRef(self, ctx: SqlBaseParser.WindowRefContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#windowDef. + def visitWindowDef(self, ctx: SqlBaseParser.WindowDefContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#windowFrame. + def visitWindowFrame(self, ctx: SqlBaseParser.WindowFrameContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#frameBound. + def visitFrameBound(self, ctx: SqlBaseParser.FrameBoundContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#qualifiedNameList. + def visitQualifiedNameList(self, ctx: SqlBaseParser.QualifiedNameListContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#functionName. + def visitFunctionName(self, ctx: SqlBaseParser.FunctionNameContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#qualifiedName. + def visitQualifiedName(self, ctx: SqlBaseParser.QualifiedNameContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#errorCapturingIdentifier. + def visitErrorCapturingIdentifier( + self, ctx: SqlBaseParser.ErrorCapturingIdentifierContext, + ): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#errorIdent. + def visitErrorIdent(self, ctx: SqlBaseParser.ErrorIdentContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#realIdent. + def visitRealIdent(self, ctx: SqlBaseParser.RealIdentContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#identifier. + def visitIdentifier(self, ctx: SqlBaseParser.IdentifierContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#unquotedIdentifier. + def visitUnquotedIdentifier(self, ctx: SqlBaseParser.UnquotedIdentifierContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#quotedIdentifierAlternative. + def visitQuotedIdentifierAlternative( + self, ctx: SqlBaseParser.QuotedIdentifierAlternativeContext, + ): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#quotedIdentifier. + def visitQuotedIdentifier(self, ctx: SqlBaseParser.QuotedIdentifierContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#backQuotedIdentifier. + def visitBackQuotedIdentifier(self, ctx: SqlBaseParser.BackQuotedIdentifierContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#exponentLiteral. + def visitExponentLiteral(self, ctx: SqlBaseParser.ExponentLiteralContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#decimalLiteral. + def visitDecimalLiteral(self, ctx: SqlBaseParser.DecimalLiteralContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#integerLiteral. + def visitIntegerLiteral(self, ctx: SqlBaseParser.IntegerLiteralContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#bigIntLiteral. + def visitBigIntLiteral(self, ctx: SqlBaseParser.BigIntLiteralContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#smallIntLiteral. + def visitSmallIntLiteral(self, ctx: SqlBaseParser.SmallIntLiteralContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#tinyIntLiteral. + def visitTinyIntLiteral(self, ctx: SqlBaseParser.TinyIntLiteralContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#doubleLiteral. + def visitDoubleLiteral(self, ctx: SqlBaseParser.DoubleLiteralContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#floatLiteral. + def visitFloatLiteral(self, ctx: SqlBaseParser.FloatLiteralContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#bigDecimalLiteral. + def visitBigDecimalLiteral(self, ctx: SqlBaseParser.BigDecimalLiteralContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#alterColumnAction. + def visitAlterColumnAction(self, ctx: SqlBaseParser.AlterColumnActionContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#stringLit. + def visitStringLit(self, ctx: SqlBaseParser.StringLitContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#comment. + def visitComment(self, ctx: SqlBaseParser.CommentContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#version. + def visitVersion(self, ctx: SqlBaseParser.VersionContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#ansiNonReserved. + def visitAnsiNonReserved(self, ctx: SqlBaseParser.AnsiNonReservedContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#strictNonReserved. + def visitStrictNonReserved(self, ctx: SqlBaseParser.StrictNonReservedContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by SqlBaseParser#nonReserved. + def visitNonReserved(self, ctx: SqlBaseParser.NonReservedContext): + return self.visitChildren(ctx) + + +del SqlBaseParser diff --git a/datajunction-server/datajunction_server/sql/parsing/types.py b/datajunction-server/datajunction_server/sql/parsing/types.py new file mode 100644 index 000000000..754714a39 --- /dev/null +++ b/datajunction-server/datajunction_server/sql/parsing/types.py @@ -0,0 +1,929 @@ +"""DJ Column Types + + Example: + >>> StructType( \ + NestedField("required_field", StringType(), False, "a required field"), \ + NestedField("optional_field", IntegerType(), True, "an optional field") \ + ) + StructType(NestedField(name=Name(name='required_field', quote_style='', namespace=None), \ +field_type=StringType(), is_optional=False, doc='a required field'), \ +NestedField(name=Name(name='optional_field', quote_style='', namespace=None), \ +field_type=IntegerType(), is_optional=True, doc='an optional field')) + +""" + +import re +from enum import Enum +from typing import TYPE_CHECKING, Any, ClassVar, Dict, Generator, Optional, Tuple, cast + +from pydantic import BaseModel, Extra +from pydantic.class_validators import AnyCallable + +if TYPE_CHECKING: + from datajunction_server.sql.parsing import ast + + +DECIMAL_REGEX = re.compile(r"(?i)decimal\((?P\d+),\s*(?P\d+)\)") +FIXED_PARSER = re.compile(r"(?i)fixed\((?P\d+)\)") +VARCHAR_PARSER = re.compile(r"(?i)varchar(\((?P\d+)\))?") + + +class Singleton: # pylint: disable=too-few-public-methods + """ + Singleton for types + """ + + _instance = None + + def __new__(cls, *args, **kwargs): # pylint: disable=unused-argument + if not isinstance(cls._instance, cls): + cls._instance = super(Singleton, cls).__new__(cls) + return cls._instance + + +class ColumnType(BaseModel): + """ + Base type for all Column Types + """ + + _initialized = False + + class Config: # pylint: disable=missing-class-docstring, too-few-public-methods + extra = Extra.allow + arbitrary_types_allowed = True + underscore_attrs_are_private = False + + def __init__( # pylint: disable=keyword-arg-before-vararg + self, + type_string: str, + repr_string: str = None, + *args, + **kwargs, + ): + super().__init__(*args, **kwargs) + self._type_string = type_string + self._repr_string = repr_string if repr_string else self._type_string + self._initialized = True + + def __repr__(self): + return self._repr_string + + def __str__(self): + return self._type_string + + def __deepcopy__(self, memo): + return self + + @classmethod + def __get_validators__(cls) -> Generator[AnyCallable, None, None]: + """ + One or more validators may be yielded which will be called in the + order to validate the input, each validator will receive as an input + the value returned from the previous validator + """ + yield cls.validate + + @classmethod + def validate( # pylint: disable=too-many-return-statements + cls, + v: Any, + ) -> "ColumnType": + """ + Parses the column type + """ + from datajunction_server.sql.parsing.backends.antlr4 import ( # pylint: disable=import-outside-toplevel + parse_rule, + ) + + return cast(ColumnType, parse_rule(str(v), "dataType")) + + def __eq__(self, other: "ColumnType"): # type: ignore + """ + Equality is dependent on the string representation of the column type. + """ + return str(other) == str(self) + + def __hash__(self): + """ + Equality is dependent on the string representation of the column type. + """ + return hash(str(self)) + + def is_compatible(self, other: "ColumnType") -> bool: + """ + Returns whether the two types are compatible with each other by + checking their ancestors. + """ + if self == NullType() or other == NullType() or self == other: + return True # quick return + + def has_common_ancestor(type1, type2) -> bool: + """ + Helper function to check whether two column types have common ancestors, + other than the highest-level ancestor types like ColumnType itself. This + determines whether they're part of the same type group and are compatible + with each other when performing type compatibility checks. + """ + base_types = (ColumnType, Singleton, PrimitiveType) + if type1 in base_types or type2 in base_types: + return False + if type1 == type2: + return True + current_has = False + for ancestor in type1.__bases__: + for ancestor2 in type2.__bases__: + current_has = current_has or has_common_ancestor( + ancestor, + ancestor2, + ) + if current_has: + return current_has + return False + + return has_common_ancestor(self.__class__, other.__class__) + + +class PrimitiveType(ColumnType): # pylint: disable=too-few-public-methods + """Base class for all Column Primitive Types""" + + +class NumberType(PrimitiveType): # pylint: disable=too-few-public-methods + """Base class for all Column Number Types""" + + +class NullType(PrimitiveType, Singleton): # pylint: disable=too-few-public-methods + """A data type for NULL + + Example: + >>> NullType() + NullType() + """ + + def __init__(self): + super().__init__("NULL", "NullType()") + + +class FixedType(PrimitiveType): + """A fixed data type. + + Example: + >>> FixedType(8) + FixedType(length=8) + >>> FixedType(8)==FixedType(8) + True + """ + + _instances: Dict[int, "FixedType"] = {} + + def __new__(cls, length: int): + cls._instances[length] = cls._instances.get(length) or object.__new__(cls) + return cls._instances[length] + + def __init__(self, length: int): + if not self._initialized: + super().__init__(f"fixed({length})", f"FixedType(length={length})") + self._length = length + + @property + def length(self) -> int: # pragma: no cover + """ + The length of the fixed type + """ + return self._length + + +class LambdaType(ColumnType): + """ + Type representing a lambda function + """ + + +class DecimalType(NumberType): + """A fixed data type. + + Example: + >>> DecimalType(32, 3) + DecimalType(precision=32, scale=3) + >>> DecimalType(8, 3)==DecimalType(8, 3) + True + """ + + max_precision: ClassVar[int] = 38 + max_scale: ClassVar[int] = 38 + _instances: Dict[Tuple[int, int], "DecimalType"] = {} + + def __new__(cls, precision: int, scale: int): + key = ( + min(precision, DecimalType.max_precision), + min(scale, DecimalType.max_scale), + ) + cls._instances[key] = cls._instances.get(key) or object.__new__(cls) + return cls._instances[key] + + def __init__(self, precision: int, scale: int): + if not self._initialized: + super().__init__( + f"decimal({precision}, {scale})", + f"DecimalType(precision={precision}, scale={scale})", + ) + self._precision = min(precision, DecimalType.max_precision) + self._scale = min(scale, DecimalType.max_scale) + + @property + def precision(self) -> int: # pragma: no cover + """ + Decimal's precision + """ + return self._precision + + @property + def scale(self) -> int: # pragma: no cover + """ + Decimal's scale + """ + return self._scale + + +class NestedField(ColumnType): + """Represents a field of a struct, a map key, a map value, or a list element. + + This is where field IDs, names, docs, and nullability are tracked. + """ + + _instances: Dict[ + Tuple[bool, str, ColumnType, Optional[str]], + "NestedField", + ] = {} + + def __new__( + cls, + name: "ast.Name", + field_type: ColumnType, + is_optional: bool = True, + doc: Optional[str] = None, + ): + if isinstance(name, str): # pragma: no cover + from datajunction_server.sql.parsing.ast import ( # pylint: disable=import-outside-toplevel + Name, + ) + + name = Name(name) + + key = (is_optional, name.name, field_type, doc) + cls._instances[key] = cls._instances.get(key) or object.__new__(cls) + return cls._instances[key] + + def __init__( + self, + name: "ast.Name", + field_type: ColumnType, + is_optional: bool = True, + doc: Optional[str] = None, + ): + if not self._initialized: + if isinstance(name, str): # pragma: no cover + from datajunction_server.sql.parsing.ast import ( # pylint: disable=import-outside-toplevel + Name, + ) + + name = Name(name) + doc_string = "" if doc is None else f", doc={repr(doc)}" + super().__init__( + ( + f"{name}: {field_type}" + f"{' NOT NULL' if not is_optional else ''}" + + ("" if doc is None else f" {doc}") + ), + f"NestedField(name={repr(name)}, " + f"field_type={repr(field_type)}, " + f"is_optional={is_optional}" + f"{doc_string})", + ) + self._is_optional = is_optional + self._name = name + self._type = field_type + self._doc = doc + + @property + def is_optional(self) -> bool: + """ + Whether the field is optional + """ + return self._is_optional # pragma: no cover + + @property + def is_required(self) -> bool: + """ + Whether the field is required + """ + return not self._is_optional # pragma: no cover + + @property + def name(self) -> "ast.Name": + """ + The name of the field + """ + return self._name + + @property + def doc(self) -> Optional[str]: + """ + The docstring of the field + """ + return self._doc # pragma: no cover + + @property + def type(self) -> ColumnType: + """ + The field's type + """ + return self._type + + +class StructType(ColumnType): + """A struct type + + Example: + >>> StructType( \ + NestedField("required_field", StringType(), False, "a required field"), \ + NestedField("optional_field", IntegerType(), True, "an optional field") \ + ) + StructType(NestedField(name=Name(name='required_field', quote_style='', namespace=None), \ +field_type=StringType(), is_optional=False, doc='a required field'), \ +NestedField(name=Name(name='optional_field', quote_style='', namespace=None), \ +field_type=IntegerType(), is_optional=True, doc='an optional field')) + """ + + _instances: Dict[Tuple[NestedField, ...], "StructType"] = {} + + def __new__(cls, *fields: NestedField): + cls._instances[fields] = cls._instances.get(fields) or object.__new__(cls) + return cls._instances[fields] + + def __init__(self, *fields: NestedField): + if not self._initialized: + super().__init__( + f"struct<{', '.join(map(str, fields))}>", + f"StructType{repr(fields)}", + ) + self._fields = fields + + @property + def fields(self) -> Tuple[NestedField, ...]: + """ + Returns the struct's fields. + """ + return self._fields # pragma: no cover + + +class ListType(ColumnType): + """A list type + + Example: + >>> ListType(element_type=StringType()) + ListType(element_type=StringType()) + """ + + _instances: Dict[Tuple[bool, int, ColumnType], "ListType"] = {} + + def __new__( + cls, + element_type: ColumnType, + ): + key = (element_type,) + cls._instances[key] = cls._instances.get(key) or object.__new__(cls) # type: ignore + return cls._instances[key] # type: ignore + + def __init__( + self, + element_type: ColumnType, + ): + if not self._initialized: + super().__init__( + f"array<{element_type}>", + f"ListType(element_type={repr(element_type)})", + ) + self._element_field = NestedField( + name="col", # type: ignore + field_type=element_type, + is_optional=False, # type: ignore + ) + + @property + def element(self) -> NestedField: + """ + Returns the list's element + """ + return self._element_field + + +class MapType(ColumnType): + """A map type""" + + _instances: Dict[Tuple[ColumnType, ColumnType], "MapType"] = {} + + def __new__( + cls, + key_type: ColumnType, + value_type: ColumnType, + ): + impl_key = (key_type, value_type) + cls._instances[impl_key] = cls._instances.get(impl_key) or object.__new__(cls) + return cls._instances[impl_key] + + def __init__( + self, + key_type: ColumnType, + value_type: ColumnType, + ): + if not self._initialized: + super().__init__( + f"map<{key_type}, {value_type}>", + ) + self._key_field = NestedField( + name="key", # type: ignore + field_type=key_type, + is_optional=False, # type: ignore + ) + self._value_field = NestedField( + name="value", # type: ignore + field_type=value_type, + is_optional=False, # type: ignore + ) + + @property + def key(self) -> NestedField: + """ + The map's key + """ + return self._key_field + + @property + def value(self) -> NestedField: + """ + The map's value + """ + return self._value_field + + +class BooleanType(PrimitiveType, Singleton): + """A boolean data type can be represented using an instance of this class. + + Example: + >>> column_foo = BooleanType() + >>> isinstance(column_foo, BooleanType) + True + """ + + def __init__(self): + super().__init__("boolean", "BooleanType()") + + +class IntegerBase(NumberType, Singleton): + """Base class for all integer types""" + + max: ClassVar[int] + min: ClassVar[int] + + def check_bounds(self, value: int) -> bool: + """ + Check whether a value fits within the Integer min and max + """ + return self.__class__.min < value < self.__class__.max + + +class IntegerType(IntegerBase): + """An Integer data type can be represented using an instance of this class. Integers are + 32-bit signed and can be promoted to Longs. + + Example: + >>> column_foo = IntegerType() + >>> isinstance(column_foo, IntegerType) + True + + Attributes: + max (int): The maximum allowed value for Integers, inherited from the + canonical Column implementation + in Java (returns `2147483647`) + min (int): The minimum allowed value for Integers, inherited from the + canonical Column implementation + in Java (returns `-2147483648`) + """ + + max: ClassVar[int] = 2147483647 + + min: ClassVar[int] = -2147483648 + + def __init__(self): + super().__init__("int", "IntegerType()") + + +class TinyIntType(IntegerBase): + """A TinyInt data type can be represented using an instance of this class. TinyInts are + 8-bit signed integers. + + Example: + >>> column_foo = TinyIntType() + >>> isinstance(column_foo, TinyIntType) + True + + Attributes: + max (int): The maximum allowed value for TinyInts (returns `127`). + min (int): The minimum allowed value for TinyInts (returns `-128`). + """ + + max: ClassVar[int] = 127 + + min: ClassVar[int] = -128 + + def __init__(self): + super().__init__("tinyint", "TinyIntType()") + + +class SmallIntType(IntegerBase): # pylint: disable=R0901 + """A SmallInt data type can be represented using an instance of this class. SmallInts are + 16-bit signed integers. + + Example: + >>> column_foo = SmallIntType() + >>> isinstance(column_foo, SmallIntType) + True + + Attributes: + max (int): The maximum allowed value for SmallInts (returns `32767`). + min (int): The minimum allowed value for SmallInts (returns `-32768`). + """ + + max: ClassVar[int] = 32767 + + min: ClassVar[int] = -32768 + + def __init__(self): + super().__init__("smallint", "SmallIntType()") + + +class BigIntType(IntegerBase): + """A Long data type can be represented using an instance of this class. Longs are + 64-bit signed integers. + + Example: + >>> column_foo = BigIntType() + >>> isinstance(column_foo, BigIntType) + True + + Attributes: + max (int): The maximum allowed value for Longs, inherited from the + canonical Column implementation + in Java. (returns `9223372036854775807`) + min (int): The minimum allowed value for Longs, inherited from the + canonical Column implementation + in Java (returns `-9223372036854775808`) + """ + + max: ClassVar[int] = 9223372036854775807 + + min: ClassVar[int] = -9223372036854775808 + + def __init__(self): + super().__init__("bigint", "BigIntType()") + + +class LongType(BigIntType): # pylint: disable=R0901 + """A Long data type can be represented using an instance of this class. Longs are + 64-bit signed integers. + + Example: + >>> column_foo = LongType() + >>> column_foo == LongType() + True + + Attributes: + max (int): The maximum allowed value for Longs, inherited from the + canonical Column implementation + in Java. (returns `9223372036854775807`) + min (int): The minimum allowed value for Longs, inherited from the + canonical Column implementation + in Java (returns `-9223372036854775808`) + """ + + def __new__(cls, *args, **kwargs): + self = super().__new__(BigIntType, *args, **kwargs) + super(BigIntType, self).__init__("long", "LongType()") + return self + + +class FloatingBase(NumberType, Singleton): + """Base class for all floating types""" + + +class FloatType(FloatingBase): + """A Float data type can be represented using an instance of this class. Floats are + 32-bit IEEE 754 floating points and can be promoted to Doubles. + + Example: + >>> column_foo = FloatType() + >>> isinstance(column_foo, FloatType) + True + """ + + def __init__(self): + super().__init__("float", "FloatType()") + + +class DoubleType(FloatingBase): + """A Double data type can be represented using an instance of this class. Doubles are + 64-bit IEEE 754 floating points. + + Example: + >>> column_foo = DoubleType() + >>> isinstance(column_foo, DoubleType) + True + """ + + def __init__(self): + super().__init__("double", "DoubleType()") + + +class DateTimeBase(PrimitiveType, Singleton): + """ + Base class for date and time types. + """ + + # pylint: disable=invalid-name + class Unit(str, Enum): + """ + Units used for date and time functions and intervals + """ + + dayofyear = "DAYOFYEAR" + year = "YEAR" + day = "DAY" + microsecond = "MICROSECOND" + month = "MONTH" + week = "WEEK" + minute = "MINUTE" + second = "SECOND" + quarter = "QUARTER" + hour = "HOUR" + millisecond = "MILLISECOND" + + # pylint: enable=invalid-name + + +class DateType(DateTimeBase): + """A Date data type can be represented using an instance of this class. Dates are + calendar dates without a timezone or time. + + Example: + >>> column_foo = DateType() + >>> isinstance(column_foo, DateType) + True + """ + + def __init__(self): + super().__init__("date", "DateType()") + + +class TimeType(DateTimeBase): + """A Time data type can be represented using an instance of this class. Times + have microsecond precision and are a time of day without a date or timezone. + + Example: + >>> column_foo = TimeType() + >>> isinstance(column_foo, TimeType) + True + """ + + def __init__(self): + super().__init__("time", "TimeType()") + + +class TimestampType(DateTimeBase): + """A Timestamp data type can be represented using an instance of this class. Timestamps in + Column have microsecond precision and include a date and a time of day without a timezone. + + Example: + >>> column_foo = TimestampType() + >>> isinstance(column_foo, TimestampType) + True + """ + + def __init__(self): + super().__init__("timestamp", "TimestampType()") + + +class TimestamptzType(PrimitiveType, Singleton): + """A Timestamptz data type can be represented using an instance of this class. Timestamptzs in + Column are stored as UTC and include a date and a time of day with a timezone. + + Example: + >>> column_foo = TimestamptzType() + >>> isinstance(column_foo, TimestamptzType) + True + """ + + def __init__(self): + super().__init__("timestamptz", "TimestamptzType()") + + +class IntervalTypeBase(PrimitiveType): + """A base class for all interval types""" + + +class DayTimeIntervalType(IntervalTypeBase): + """A DayTimeIntervalType type. + + Example: + >>> DayTimeIntervalType()==DayTimeIntervalType("DAY", "SECOND") + True + """ + + _instances: Dict[Tuple[str, str], "DayTimeIntervalType"] = {} + + def __new__( + cls, + from_: DateTimeBase.Unit = DateTimeBase.Unit.day, + to_: Optional[DateTimeBase.Unit] = DateTimeBase.Unit.second, + ): + key = (from_.upper(), to_.upper()) # type: ignore + cls._instances[key] = cls._instances.get(key) or object.__new__(cls) + return cls._instances[key] + + def __init__( + self, + from_: DateTimeBase.Unit = DateTimeBase.Unit.day, + to_: Optional[DateTimeBase.Unit] = DateTimeBase.Unit.second, + ): + if not self._initialized: + from_ = from_.upper() # type: ignore + to_ = to_.upper() # type: ignore + to_str = f" TO {to_}" if to_ else "" + to_repr = f', to="{to_}"' if to_ else "" + super().__init__( + f"INTERVAL {from_}{to_str}", + f'DayTimeIntervalType(from="{from_}"{to_repr})', + ) + self._from = from_ + self._to = to_ + + @property + def from_(self) -> str: # pylint: disable=missing-function-docstring + return self._from # pragma: no cover + + @property + def to_( # pylint: disable=missing-function-docstring + self, + ) -> Optional[str]: + return self._to # pragma: no cover + + +class YearMonthIntervalType(IntervalTypeBase): + """A YearMonthIntervalType type. + + Example: + >>> YearMonthIntervalType()==YearMonthIntervalType("YEAR", "MONTH") + True + """ + + _instances: Dict[Tuple[str, str], "YearMonthIntervalType"] = {} + + def __new__( + cls, + from_: DateTimeBase.Unit = DateTimeBase.Unit.year, + to_: Optional[DateTimeBase.Unit] = DateTimeBase.Unit.month, + ): + key = (from_.upper(), to_.upper()) # type: ignore + cls._instances[key] = cls._instances.get(key) or object.__new__(cls) + return cls._instances[key] + + def __init__( + self, + from_: DateTimeBase.Unit = DateTimeBase.Unit.year, + to_: Optional[DateTimeBase.Unit] = DateTimeBase.Unit.month, + ): + if not self._initialized: + from_ = from_.upper() # type: ignore + to_ = to_.upper() # type: ignore + to_str = f" TO {to_}" if to_ else "" + to_repr = f', to="{to_}"' if to_ else "" + super().__init__( + f"INTERVAL {from_}{to_str}", + f'YearMonthIntervalType(from="{from_}"{to_repr})', + ) + self._from = from_ + self._to = to_ + + @property + def from_(self) -> str: # pylint: disable=missing-function-docstring + return self._from # pragma: no cover + + @property + def to_( # pylint: disable=missing-function-docstring + self, + ) -> Optional[str]: + return self._to # pragma: no cover + + +class StringBase(PrimitiveType, Singleton): + """Base class for all string types""" + + +class StringType(StringBase): + """A String data type can be represented using an instance of this class. Strings in + Column are arbitrary-length character sequences and are encoded with UTF-8. + + Example: + >>> column_foo = StringType() + >>> isinstance(column_foo, StringType) + True + """ + + def __init__(self): + super().__init__("string", "StringType()") + + +class VarcharType(StringBase): + """A VarcharType data type can be represented using an instance of this class. + Varchars in Column are arbitrary-length character sequences and are + encoded with UTF-8. + + Example: + >>> column_foo = VarcharType() + >>> isinstance(column_foo, VarcharType) + True + """ + + def __init__(self, length: Optional[int] = None): + super().__init__("varchar", "VarcharType()") + self._length = length + + def __str__(self): + return ( + f"{self._type_string}({self._length})" + if self._length + else self._type_string + ) + + +class UUIDType(PrimitiveType, Singleton): + """A UUID data type can be represented using an instance of this class. UUIDs in + Column are universally unique identifiers. + + Example: + >>> column_foo = UUIDType() + >>> isinstance(column_foo, UUIDType) + True + """ + + def __init__(self): + super().__init__("uuid", "UUIDType()") + + +class BinaryType(PrimitiveType, Singleton): + """A Binary data type can be represented using an instance of this class. Binarys in + Column are arbitrary-length byte arrays. + + Example: + >>> column_foo = BinaryType() + >>> isinstance(column_foo, BinaryType) + True + """ + + def __init__(self): + super().__init__("binary", "BinaryType()") + + +class WildcardType(PrimitiveType, Singleton): + """A Wildcard datatype. + + Example: + >>> column_foo = WildcardType() + >>> isinstance(column_foo, WildcardType) + True + """ + + def __init__(self): + super().__init__("wildcard", "WildcardType()") + + +# Define the primitive data types and their corresponding Python classes +PRIMITIVE_TYPES: Dict[str, PrimitiveType] = { + "bool": BooleanType(), + "boolean": BooleanType(), + "varchar": VarcharType(), + "bigint": BigIntType(), + "int": IntegerType(), + "long": BigIntType(), + "float": FloatType(), + "double": DoubleType(), + "date": DateType(), + "time": TimeType(), + "timestamp": TimestampType(), + "timestamptz": TimestamptzType(), + "string": StringType(), + "uuid": UUIDType(), + "byte": BinaryType(), + "binary": BinaryType(), + "none": NullType(), + "null": NullType(), +} diff --git a/datajunction-server/datajunction_server/superset.py b/datajunction-server/datajunction_server/superset.py new file mode 100644 index 000000000..eb4979dd6 --- /dev/null +++ b/datajunction-server/datajunction_server/superset.py @@ -0,0 +1,137 @@ +""" +A DB engine spec for Superset. +""" + +import re +from datetime import timedelta +from typing import TYPE_CHECKING, Any, List, Optional, Set, TypedDict + +import requests +from sqlalchemy.engine.reflection import Inspector + +try: + from superset.db_engine_specs.base import BaseEngineSpec +except ImportError: # pragma: no cover + # we don't really need the base class, so we can just mock it if Apache Superset is + # not installed + BaseEngineSpec = object + +if TYPE_CHECKING: + from superset.models.core import Database + + +SELECT_STAR_MESSAGE = ( + "DJ does not support data preview, since the `metrics` table is a virtual table " + "representing the whole repository of metrics. An administrator should configure the " + "DJ database with the `disable_data_preview` attribute set to `true` in the `extra` " + "field." +) +GET_METRICS_TIMEOUT = timedelta(seconds=60) + + +class MetricType(TypedDict, total=False): + """ + Type for metrics return by `get_metrics`. + """ + + metric_name: str + expression: str + verbose_name: Optional[str] + metric_type: Optional[str] + description: Optional[str] + d3format: Optional[str] + warning_text: Optional[str] + extra: Optional[str] + + +class DJEngineSpec(BaseEngineSpec): # pylint: disable=abstract-method + """ + Engine spec for the DataJunction metric repository + + See https://github.com/DataJunction/dj for more information. + """ + + engine = "dj" + engine_name = "DJ" + + sqlalchemy_uri_placeholder = "dj://host:port/database_id" + + _time_grain_expressions = { + None: "{col}", + "PT1S": "DATE_TRUNC('second', {col})", + "PT1M": "DATE_TRUNC('minute', {col})", + "PT1H": "DATE_TRUNC('hour', {col})", + "P1D": "DATE_TRUNC('day', {col})", + "P1W": "DATE_TRUNC('week', {col})", + "P1M": "DATE_TRUNC('month', {col})", + "P3M": "DATE_TRUNC('quarter', {col})", + "P1Y": "DATE_TRUNC('year', {col})", + } + + @classmethod + def select_star( # pylint: disable=unused-argument + cls, + *args: Any, + **kwargs: Any, + ) -> str: + """ + Return a ``SELECT *`` query. + + Since DJ doesn't have tables per se, a ``SELECT *`` query doesn't make sense. + """ + message = SELECT_STAR_MESSAGE.replace("'", "''") + return f"SELECT '{message}' AS warning" + + @classmethod + def get_metrics( # pylint: disable=unused-argument + cls, + database: "Database", + inspector: Inspector, + table_name: str, + schema: Optional[str], + ) -> List[MetricType]: + """ + Get all metrics from a given schema and table. + """ + with database.get_sqla_engine_with_context() as engine: + base_url = engine.connect().connection.base_url + + response = requests.get( + base_url / "metrics/", + timeout=GET_METRICS_TIMEOUT.total_seconds(), + ) + payload = response.json() + return [ + { + "metric_name": metric_name, + "expression": f'"{metric_name}"', + "description": "", + } + for metric_name in payload + ] + + @classmethod + def execute( + cls, + cursor: Any, + query: str, + **kwargs: Any, + ) -> None: + """ + Quote ``__timestamp`` and other identifiers starting with an underscore. + """ + query = re.sub(r" AS (_.*?)(\b|$)", r' AS "\1"', query) + + return super().execute(cursor, query, **kwargs) + + @classmethod + def get_view_names( # pylint: disable=unused-argument + cls, + database: "Database", + inspector: Inspector, + schema: Optional[str], + ) -> Set[str]: + """ + Return all views. + """ + return set() diff --git a/datajunction-server/datajunction_server/typing.py b/datajunction-server/datajunction_server/typing.py new file mode 100644 index 000000000..a73117c1e --- /dev/null +++ b/datajunction-server/datajunction_server/typing.py @@ -0,0 +1,332 @@ +""" +Custom types for annotations. +""" + +# pylint: disable=missing-class-docstring + +from __future__ import annotations + +import datetime +from enum import Enum +from types import ModuleType +from typing import Any, Iterator, List, Literal, Optional, Tuple, TypedDict, Union + +from pydantic.datetime_parse import parse_datetime +from typing_extensions import Protocol + + +class SQLADialect(Protocol): # pylint: disable=too-few-public-methods + """ + A SQLAlchemy dialect. + """ + + dbapi: ModuleType + + +# The ``type_code`` in a cursor description -- can really be anything +TypeCode = Any + + +# Cursor description +Description = Optional[ + List[ + Tuple[ + str, + TypeCode, + Optional[str], + Optional[str], + Optional[str], + Optional[str], + Optional[bool], + ] + ] +] + + +# A stream of data +Row = Tuple[Any, ...] +Stream = Iterator[Row] + + +class TypeEnum(str, Enum): + """ + PEP 249 basic types. + + Unfortunately SQLAlchemy doesn't seem to offer an API for determining the types of the + columns in a (SQL Core) query, and the DB API 2.0 cursor only offers very coarse + types. + """ + + STRING = "STRING" + BINARY = "BINARY" + NUMBER = "NUMBER" + TIMESTAMP = "TIMESTAMP" + UNKNOWN = "UNKNOWN" + + +class QueryState(str, Enum): + """ + Different states of a query. + """ + + UNKNOWN = "UNKNOWN" + ACCEPTED = "ACCEPTED" + SCHEDULED = "SCHEDULED" + RUNNING = "RUNNING" + FINISHED = "FINISHED" + CANCELED = "CANCELED" + FAILED = "FAILED" + + +END_JOB_STATES = [QueryState.FINISHED, QueryState.CANCELED, QueryState.FAILED] + +# sqloxide type hints +# Reference: https://github.com/sqlparser-rs/sqlparser-rs/blob/main/src/ast/query.rs + + +class Value(TypedDict, total=False): + Number: Tuple[str, bool] + SingleQuotedString: str + Boolean: bool + + +class Limit(TypedDict): + Value: Value + + +class Identifier(TypedDict): + quote_style: Optional[str] + value: str + + +class Bound(TypedDict, total=False): + Following: int + Preceding: int + + +class WindowFrame(TypedDict): + end_bound: Bound + start_bound: Bound + units: str + + +class Expression(TypedDict, total=False): + CompoundIdentifier: List["Identifier"] + Identifier: Identifier + Value: Value + Function: Function # type: ignore + UnaryOp: UnaryOp # type: ignore + BinaryOp: BinaryOp # type: ignore + Case: Case # type: ignore + + +class Case(TypedDict): + conditions: List[Expression] + else_result: Optional[Expression] + operand: Optional[Expression] + results: List[Expression] + + +class UnnamedArgument(TypedDict): + Expr: Expression + + +class Argument(TypedDict, total=False): + Unnamed: Union[UnnamedArgument, Wildcard] + + +class Over(TypedDict): + order_by: List[Expression] + partition_by: List[Expression] + window_frame: WindowFrame + + +class Function(TypedDict): + args: List[Argument] + distinct: bool + name: List[Identifier] + over: Optional[Over] + + +class ExpressionWithAlias(TypedDict): + alias: Identifier + expr: Expression + + +class Offset(TypedDict): + rows: str + value: Expression + + +class OrderBy(TypedDict, total=False): + asc: Optional[bool] + expr: Expression + nulls_first: Optional[bool] + + +class Projection(TypedDict, total=False): + ExprWithAlias: ExpressionWithAlias + UnnamedExpr: Expression + + +Wildcard = Literal["Wildcard"] + + +class Fetch(TypedDict): + percent: bool + quantity: Value + with_ties: bool + + +Top = Fetch + + +class UnaryOp(TypedDict): + op: str + expr: Expression + + +class BinaryOp(TypedDict): + left: Expression + op: str + right: Expression + + +class LateralView(TypedDict): + lateral_col_alias: List[Identifier] + lateral_view: Expression + lateral_view_name: List[Identifier] + outer: bool + + +class TableAlias(TypedDict): + columns: List[Identifier] + name: Identifier + + +class Table(TypedDict): + alias: Optional[TableAlias] + args: List[Argument] + name: List[Identifier] + with_hints: List[Expression] + + +class Derived(TypedDict): + lateral: bool + subquery: "Body" # type: ignore + alias: Optional[TableAlias] + + +class Relation(TypedDict, total=False): + Table: Table + Derived: Derived + + +class JoinConstraint(TypedDict): + On: Expression + Using: List[Identifier] + + +class JoinOperator(TypedDict, total=False): + Inner: JoinConstraint + LeftOuter: JoinConstraint + RightOuter: JoinConstraint + FullOuter: JoinConstraint + + +CrossJoin = Literal["CrossJoin"] +CrossApply = Literal["CrossApply"] +OuterApply = Literal["Outerapply"] + + +class Join(TypedDict): + join_operator: Union[JoinOperator, CrossJoin, CrossApply, OuterApply] + relation: Relation + + +class From(TypedDict): + joins: List[Join] + relation: Relation + + +Select = TypedDict( + "Select", + { + "cluster_by": List[Expression], + "distinct": bool, + "distribute_by": List[Expression], + "from": List[From], + "group_by": List[Expression], + "having": Optional[BinaryOp], + "lateral_views": List[LateralView], + "projection": List[Union[Projection, Wildcard]], + "selection": Optional[BinaryOp], + "sort_by": List[Expression], + "top": Optional[Top], + }, +) + + +class Body(TypedDict): + Select: Select + + +CTETable = TypedDict( + "CTETable", + { + "alias": TableAlias, + "from": Optional[Identifier], + "query": "Query", # type: ignore + }, +) + + +class With(TypedDict): + cte_tables: List[CTETable] + + +Query = TypedDict( + "Query", + { + "body": Body, + "fetch": Optional[Fetch], + "limit": Optional[Limit], + "lock": Optional[Literal["Share", "Update"]], + "offset": Optional[Offset], + "order_by": List[OrderBy], + "with": Optional[With], + }, +) + + +# We could support more than just ``SELECT`` here. +class Statement(TypedDict): + Query: Query + + +# A parse tree, result of ``sqloxide.parse_sql``. +ParseTree = List[Statement] # type: ignore + + +class UTCDatetime(datetime.datetime): + """ + A UTC extension of pydantic's normal datetime handling + """ + + @classmethod + def __get_validators__(cls): + """ + Extend the builtin pydantic datetime parser with a custom validate method + """ + yield parse_datetime + yield cls.validate + + @classmethod + def validate(cls, value) -> str: + """ + Convert to UTC + """ + if value.tzinfo is None: + return value.replace(tzinfo=datetime.timezone.utc) + + return value.astimezone(datetime.timezone.utc) diff --git a/datajunction-server/datajunction_server/utils.py b/datajunction-server/datajunction_server/utils.py new file mode 100644 index 000000000..e70d1727d --- /dev/null +++ b/datajunction-server/datajunction_server/utils.py @@ -0,0 +1,205 @@ +""" +Utility functions. +""" +import logging +import os +import re +from enum import Enum +from functools import lru_cache +from string import ascii_letters, digits + +# pylint: disable=line-too-long +from typing import Iterator, List, Optional + +from dotenv import load_dotenv +from rich.logging import RichHandler +from sqlalchemy.engine import Engine +from sqlmodel import Session, create_engine +from yarl import URL + +from datajunction_server.config import Settings +from datajunction_server.errors import DJException +from datajunction_server.service_clients import QueryServiceClient + + +def setup_logging(loglevel: str) -> None: + """ + Setup basic logging. + """ + level = getattr(logging, loglevel.upper(), None) + if not isinstance(level, int): + raise ValueError(f"Invalid log level: {loglevel}") + + logformat = "[%(asctime)s] %(levelname)s: %(name)s: %(message)s" + logging.basicConfig( + level=level, + format=logformat, + datefmt="[%X]", + handlers=[RichHandler(rich_tracebacks=True)], + force=True, + ) + + +@lru_cache +def get_settings() -> Settings: + """ + Return a cached settings object. + """ + dotenv_file = os.environ.get("DOTENV_FILE", ".env") + load_dotenv(dotenv_file) + return Settings() + + +def get_engine() -> Engine: + """ + Create the metadata engine. + """ + settings = get_settings() + engine = create_engine(settings.index) + + return engine + + +def get_session() -> Iterator[Session]: + """ + Per-request session. + """ + engine = get_engine() + + with Session(engine, autoflush=False) as session: # pragma: no cover + yield session + + +def get_query_service_client() -> Optional[QueryServiceClient]: + """ + Return query service client + """ + settings = get_settings() + if not settings.query_service: # pragma: no cover + return None + return QueryServiceClient(settings.query_service) + + +def get_issue_url( + baseurl: URL = URL("https://github.com/DataJunction/dj/issues/new"), + title: Optional[str] = None, + body: Optional[str] = None, + labels: Optional[List[str]] = None, +) -> URL: + """ + Return the URL to file an issue on GitHub. + + https://docs.github.com/en/issues/tracking-your-work-with-issues/creating-an-issue#creating-an-issue-from-a-url-query + """ + query_arguments = { + "title": title, + "body": body, + "labels": ",".join(label.strip() for label in labels) if labels else None, + } + query_arguments = {k: v for k, v in query_arguments.items() if v is not None} + + return baseurl % query_arguments + + +class VersionUpgrade(str, Enum): + """ + The version upgrade type + """ + + MAJOR = "major" + MINOR = "minor" + + +class Version: + """ + Represents a basic semantic version with only major & minor parts. + Used for tracking node versioning. + """ + + def __init__(self, major, minor): + self.major = major + self.minor = minor + + def __str__(self) -> str: + return f"v{self.major}.{self.minor}" + + @classmethod + def parse(cls, version_string) -> "Version": + """ + Parse a version string. + """ + version_regex = re.compile(r"^v(?P[0-9]+)\.(?P[0-9]+)") + matcher = version_regex.search(version_string) + if not matcher: + raise DJException( + http_status_code=500, + message=f"Unparseable version {version_string}!", + ) + results = matcher.groupdict() + return Version(int(results["major"]), int(results["minor"])) + + def next_minor_version(self) -> "Version": + """ + Returns the next minor version + """ + return Version(self.major, self.minor + 1) + + def next_major_version(self) -> "Version": + """ + Returns the next major version + """ + return Version(self.major + 1, 0) + + +def get_namespace_from_name(name: str) -> str: + """ + Splits a qualified node name into it's namespace and name parts + """ + if "." in name: + node_namespace, _ = name.rsplit(".", 1) + else: # pragma: no cover + raise DJException(f"No namespace provided: {name}") + return node_namespace + + +ACCEPTABLE_CHARS = set(ascii_letters + digits + "_") +LOOKUP_CHARS = { + ".": "DOT", + "'": "QUOTE", + '"': "DQUOTE", + "`": "BTICK", + "!": "EXCL", + "@": "AT", + "#": "HASH", + "$": "DOLLAR", + "%": "PERC", + "^": "CARAT", + "&": "AMP", + "*": "STAR", + "(": "LPAREN", + ")": "RPAREN", + "[": "LBRACK", + "]": "RBRACK", + "-": "MINUS", + "+": "PLUS", + "=": "EQ", + "/": "FSLSH", + "\\": "BSLSH", + "|": "PIPE", + "~": "TILDE", +} + + +def amenable_name(name: str) -> str: + """Takes a string and makes it have only alphanumerics""" + ret: List[str] = [] + cont: List[str] = [] + for char in name: + if char in ACCEPTABLE_CHARS: + cont.append(char) + else: + ret.append("".join(cont)) + ret.append(LOOKUP_CHARS.get(char, "UNK")) + cont = [] + + return ("_".join(ret) + "_" + "".join(cont)).strip("_") diff --git a/datajunction-server/dj.demo.db b/datajunction-server/dj.demo.db new file mode 100644 index 000000000..c6a4c16ba Binary files /dev/null and b/datajunction-server/dj.demo.db differ diff --git a/datajunction-server/pdm.lock b/datajunction-server/pdm.lock new file mode 100644 index 000000000..0a81e673d --- /dev/null +++ b/datajunction-server/pdm.lock @@ -0,0 +1,2420 @@ +# This file is @generated by PDM. +# It is not intended for manual editing. + +[[package]] +name = "accept-types" +version = "0.4.1" +summary = "Determine the best content to send in an HTTP response" + +[[package]] +name = "alembic" +version = "1.11.2" +requires_python = ">=3.7" +summary = "A database migration tool for SQLAlchemy." +dependencies = [ + "Mako", + "SQLAlchemy>=1.3.0", + "importlib-metadata; python_version < \"3.9\"", + "importlib-resources; python_version < \"3.9\"", + "typing-extensions>=4", +] + +[[package]] +name = "amqp" +version = "5.1.1" +requires_python = ">=3.6" +summary = "Low-level AMQP client for Python (fork of amqplib)." +dependencies = [ + "vine>=5.0.0", +] + +[[package]] +name = "antlr4-python3-runtime" +version = "4.12.0" +summary = "ANTLR 4.12.0 runtime for Python 3" + +[[package]] +name = "anyio" +version = "3.7.1" +requires_python = ">=3.7" +summary = "High level compatibility layer for multiple asynchronous event loop implementations" +dependencies = [ + "exceptiongroup; python_version < \"3.11\"", + "idna>=2.8", + "sniffio>=1.1", +] + +[[package]] +name = "asciidag" +version = "0.2.0" +summary = "Draw DAGs (directed acyclic graphs) as ASCII art, à la git log --graph" + +[[package]] +name = "asgiref" +version = "3.7.2" +requires_python = ">=3.7" +summary = "ASGI specs, helper code, and adapters" +dependencies = [ + "typing-extensions>=4; python_version < \"3.11\"", +] + +[[package]] +name = "astroid" +version = "2.15.6" +requires_python = ">=3.7.2" +summary = "An abstract syntax tree for Python with inference support." +dependencies = [ + "lazy-object-proxy>=1.4.0", + "typing-extensions>=4.0.0; python_version < \"3.11\"", + "wrapt<2,>=1.11; python_version < \"3.11\"", + "wrapt<2,>=1.14; python_version >= \"3.11\"", +] + +[[package]] +name = "astunparse" +version = "1.6.3" +summary = "An AST unparser for Python" +dependencies = [ + "six<2.0,>=1.6.1", + "wheel<1.0,>=0.23.0", +] + +[[package]] +name = "async-timeout" +version = "4.0.3" +requires_python = ">=3.7" +summary = "Timeout context manager for asyncio programs" + +[[package]] +name = "backports-zoneinfo" +version = "0.2.1" +requires_python = ">=3.6" +summary = "Backport of the standard library zoneinfo module" + +[[package]] +name = "backports-zoneinfo" +version = "0.2.1" +extras = ["tzdata"] +requires_python = ">=3.6" +summary = "Backport of the standard library zoneinfo module" +dependencies = [ + "backports-zoneinfo==0.2.1", + "tzdata", +] + +[[package]] +name = "bcrypt" +version = "4.0.1" +requires_python = ">=3.6" +summary = "Modern password hashing for your software and your servers" + +[[package]] +name = "billiard" +version = "4.1.0" +requires_python = ">=3.7" +summary = "Python multiprocessing fork with improvements and bugfixes" + +[[package]] +name = "cachelib" +version = "0.10.2" +requires_python = ">=3.7" +summary = "A collection of cache libraries in the same API interface." + +[[package]] +name = "cachetools" +version = "5.3.1" +requires_python = ">=3.7" +summary = "Extensible memoizing collections and decorators" + +[[package]] +name = "celery" +version = "5.3.1" +requires_python = ">=3.8" +summary = "Distributed Task Queue." +dependencies = [ + "backports-zoneinfo>=0.2.1; python_version < \"3.9\"", + "billiard<5.0,>=4.1.0", + "click-didyoumean>=0.3.0", + "click-plugins>=1.1.1", + "click-repl>=0.2.0", + "click<9.0,>=8.1.2", + "kombu<6.0,>=5.3.1", + "python-dateutil>=2.8.2", + "tzdata>=2022.7", + "vine<6.0,>=5.0.0", +] + +[[package]] +name = "certifi" +version = "2023.7.22" +requires_python = ">=3.6" +summary = "Python package for providing Mozilla's CA Bundle." + +[[package]] +name = "cffi" +version = "1.15.1" +summary = "Foreign Function Interface for Python calling C code." +dependencies = [ + "pycparser", +] + +[[package]] +name = "cfgv" +version = "3.4.0" +requires_python = ">=3.8" +summary = "Validate configuration and produce human readable error messages." + +[[package]] +name = "charset-normalizer" +version = "3.2.0" +requires_python = ">=3.7.0" +summary = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." + +[[package]] +name = "click" +version = "8.1.6" +requires_python = ">=3.7" +summary = "Composable command line interface toolkit" +dependencies = [ + "colorama; platform_system == \"Windows\"", +] + +[[package]] +name = "click-didyoumean" +version = "0.3.0" +requires_python = ">=3.6.2,<4.0.0" +summary = "Enables git-like *did-you-mean* feature in click" +dependencies = [ + "click>=7", +] + +[[package]] +name = "click-plugins" +version = "1.1.1" +summary = "An extension module for click to enable registering CLI commands via setuptools entry-points." +dependencies = [ + "click>=4.0", +] + +[[package]] +name = "click-repl" +version = "0.3.0" +requires_python = ">=3.6" +summary = "REPL plugin for Click" +dependencies = [ + "click>=7.0", + "prompt-toolkit>=3.0.36", +] + +[[package]] +name = "codespell" +version = "2.2.5" +requires_python = ">=3.7" +summary = "Codespell" + +[[package]] +name = "colorama" +version = "0.4.6" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +summary = "Cross-platform colored terminal text." + +[[package]] +name = "coverage" +version = "7.3.0" +requires_python = ">=3.8" +summary = "Code coverage measurement for Python" + +[[package]] +name = "coverage" +version = "7.3.0" +extras = ["toml"] +requires_python = ">=3.8" +summary = "Code coverage measurement for Python" +dependencies = [ + "coverage==7.3.0", + "tomli; python_full_version <= \"3.11.0a6\"", +] + +[[package]] +name = "cryptography" +version = "41.0.3" +requires_python = ">=3.7" +summary = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +dependencies = [ + "cffi>=1.12", +] + +[[package]] +name = "deprecated" +version = "1.2.14" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +summary = "Python @deprecated decorator to deprecate old python classes, functions or methods." +dependencies = [ + "wrapt<2,>=1.10", +] + +[[package]] +name = "dill" +version = "0.3.7" +requires_python = ">=3.7" +summary = "serialize all of Python" + +[[package]] +name = "distlib" +version = "0.3.7" +summary = "Distribution utilities" + +[[package]] +name = "ecdsa" +version = "0.18.0" +requires_python = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +summary = "ECDSA cryptographic signature library (pure python)" +dependencies = [ + "six>=1.9.0", +] + +[[package]] +name = "exceptiongroup" +version = "1.1.3" +requires_python = ">=3.7" +summary = "Backport of PEP 654 (exception groups)" + +[[package]] +name = "execnet" +version = "2.0.2" +requires_python = ">=3.7" +summary = "execnet: rapid multi-Python deployment" + +[[package]] +name = "fastapi" +version = "0.79.1" +requires_python = ">=3.6.1" +summary = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +dependencies = [ + "pydantic!=1.7,!=1.7.1,!=1.7.2,!=1.7.3,!=1.8,!=1.8.1,<2.0.0,>=1.6.2", + "starlette==0.19.1", +] + +[[package]] +name = "filelock" +version = "3.12.2" +requires_python = ">=3.7" +summary = "A platform independent file lock." + +[[package]] +name = "freezegun" +version = "1.2.2" +requires_python = ">=3.6" +summary = "Let your Python tests travel through time" +dependencies = [ + "python-dateutil>=2.7", +] + +[[package]] +name = "graphql-core" +version = "3.2.3" +requires_python = ">=3.6,<4" +summary = "GraphQL implementation for Python, a port of GraphQL.js, the JavaScript reference implementation for GraphQL." + +[[package]] +name = "greenlet" +version = "2.0.2" +requires_python = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" +summary = "Lightweight in-process concurrent programming" + +[[package]] +name = "h11" +version = "0.14.0" +requires_python = ">=3.7" +summary = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" + +[[package]] +name = "httptools" +version = "0.6.0" +requires_python = ">=3.5.0" +summary = "A collection of framework independent HTTP protocol utils." + +[[package]] +name = "identify" +version = "2.5.26" +requires_python = ">=3.8" +summary = "File identification library for Python" + +[[package]] +name = "idna" +version = "3.4" +requires_python = ">=3.5" +summary = "Internationalized Domain Names in Applications (IDNA)" + +[[package]] +name = "importlib-metadata" +version = "6.8.0" +requires_python = ">=3.8" +summary = "Read metadata from Python packages" +dependencies = [ + "zipp>=0.5", +] + +[[package]] +name = "importlib-resources" +version = "6.0.1" +requires_python = ">=3.8" +summary = "Read resources from Python packages" +dependencies = [ + "zipp>=3.1.0; python_version < \"3.10\"", +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +requires_python = ">=3.7" +summary = "brain-dead simple config-ini parsing" + +[[package]] +name = "isort" +version = "5.12.0" +requires_python = ">=3.8.0" +summary = "A Python utility / library to sort Python imports." + +[[package]] +name = "kombu" +version = "5.3.1" +requires_python = ">=3.8" +summary = "Messaging library for Python." +dependencies = [ + "amqp<6.0.0,>=5.1.1", + "backports-zoneinfo[tzdata]>=0.2.1; python_version < \"3.9\"", + "typing-extensions; python_version < \"3.10\"", + "vine", +] + +[[package]] +name = "lazy-object-proxy" +version = "1.9.0" +requires_python = ">=3.7" +summary = "A fast and thorough lazy object proxy." + +[[package]] +name = "line-profiler" +version = "4.0.3" +requires_python = ">=3.6" +summary = "Line-by-line profiler" + +[[package]] +name = "mako" +version = "1.2.4" +requires_python = ">=3.7" +summary = "A super-fast templating language that borrows the best ideas from the existing templating languages." +dependencies = [ + "MarkupSafe>=0.9.2", +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +requires_python = ">=3.8" +summary = "Python port of markdown-it. Markdown parsing, done right!" +dependencies = [ + "mdurl~=0.1", +] + +[[package]] +name = "markupsafe" +version = "2.1.3" +requires_python = ">=3.7" +summary = "Safely add untrusted strings to HTML/XML markup." + +[[package]] +name = "mccabe" +version = "0.7.0" +requires_python = ">=3.6" +summary = "McCabe checker, plugin for flake8" + +[[package]] +name = "mdurl" +version = "0.1.2" +requires_python = ">=3.7" +summary = "Markdown URL utilities" + +[[package]] +name = "msgpack" +version = "1.0.5" +summary = "MessagePack serializer" + +[[package]] +name = "multidict" +version = "6.0.4" +requires_python = ">=3.7" +summary = "multidict implementation" + +[[package]] +name = "nodeenv" +version = "1.8.0" +requires_python = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +summary = "Node.js virtual environment builder" +dependencies = [ + "setuptools", +] + +[[package]] +name = "opentelemetry-api" +version = "1.19.0" +requires_python = ">=3.7" +summary = "OpenTelemetry Python API" +dependencies = [ + "deprecated>=1.2.6", + "importlib-metadata~=6.0", +] + +[[package]] +name = "opentelemetry-instrumentation" +version = "0.38b0" +requires_python = ">=3.7" +summary = "Instrumentation Tools & Auto Instrumentation for OpenTelemetry Python" +dependencies = [ + "opentelemetry-api~=1.4", + "setuptools>=16.0", + "wrapt<2.0.0,>=1.0.0", +] + +[[package]] +name = "opentelemetry-instrumentation-asgi" +version = "0.38b0" +requires_python = ">=3.7" +summary = "ASGI instrumentation for OpenTelemetry" +dependencies = [ + "asgiref~=3.0", + "opentelemetry-api~=1.12", + "opentelemetry-instrumentation==0.38b0", + "opentelemetry-semantic-conventions==0.38b0", + "opentelemetry-util-http==0.38b0", +] + +[[package]] +name = "opentelemetry-instrumentation-fastapi" +version = "0.38b0" +requires_python = ">=3.7" +summary = "OpenTelemetry FastAPI Instrumentation" +dependencies = [ + "opentelemetry-api~=1.12", + "opentelemetry-instrumentation-asgi==0.38b0", + "opentelemetry-instrumentation==0.38b0", + "opentelemetry-semantic-conventions==0.38b0", + "opentelemetry-util-http==0.38b0", +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.38b0" +requires_python = ">=3.7" +summary = "OpenTelemetry Semantic Conventions" + +[[package]] +name = "opentelemetry-util-http" +version = "0.38b0" +requires_python = ">=3.7" +summary = "Web util for OpenTelemetry" + +[[package]] +name = "packaging" +version = "23.1" +requires_python = ">=3.7" +summary = "Core utilities for Python packages" + +[[package]] +name = "passlib" +version = "1.7.4" +summary = "comprehensive password hashing framework supporting over 30 schemes" + +[[package]] +name = "platformdirs" +version = "3.10.0" +requires_python = ">=3.7" +summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." + +[[package]] +name = "pluggy" +version = "1.2.0" +requires_python = ">=3.7" +summary = "plugin and hook calling mechanisms for python" + +[[package]] +name = "pre-commit" +version = "3.3.3" +requires_python = ">=3.8" +summary = "A framework for managing and maintaining multi-language pre-commit hooks." +dependencies = [ + "cfgv>=2.0.0", + "identify>=1.0.0", + "nodeenv>=0.11.1", + "pyyaml>=5.1", + "virtualenv>=20.10.0", +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.39" +requires_python = ">=3.7.0" +summary = "Library for building powerful interactive command lines in Python" +dependencies = [ + "wcwidth", +] + +[[package]] +name = "pyasn1" +version = "0.5.0" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +summary = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" + +[[package]] +name = "pycparser" +version = "2.21" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +summary = "C parser in Python" + +[[package]] +name = "pydantic" +version = "1.10.12" +requires_python = ">=3.7" +summary = "Data validation and settings management using python type hints" +dependencies = [ + "typing-extensions>=4.2.0", +] + +[[package]] +name = "pygments" +version = "2.16.1" +requires_python = ">=3.7" +summary = "Pygments is a syntax highlighting package written in Python." + +[[package]] +name = "pylint" +version = "2.17.5" +requires_python = ">=3.7.2" +summary = "python code static checker" +dependencies = [ + "astroid<=2.17.0-dev0,>=2.15.6", + "colorama>=0.4.5; sys_platform == \"win32\"", + "dill>=0.2; python_version < \"3.11\"", + "dill>=0.3.6; python_version >= \"3.11\"", + "isort<6,>=4.2.5", + "mccabe<0.8,>=0.6", + "platformdirs>=2.2.0", + "tomli>=1.1.0; python_version < \"3.11\"", + "tomlkit>=0.10.1", + "typing-extensions>=3.10.0; python_version < \"3.10\"", +] + +[[package]] +name = "pytest" +version = "7.4.0" +requires_python = ">=3.7" +summary = "pytest: simple powerful testing with Python" +dependencies = [ + "colorama; sys_platform == \"win32\"", + "exceptiongroup>=1.0.0rc8; python_version < \"3.11\"", + "iniconfig", + "packaging", + "pluggy<2.0,>=0.12", + "tomli>=1.0.0; python_version < \"3.11\"", +] + +[[package]] +name = "pytest-asyncio" +version = "0.21.1" +requires_python = ">=3.7" +summary = "Pytest support for asyncio" +dependencies = [ + "pytest>=7.0.0", +] + +[[package]] +name = "pytest-cov" +version = "4.1.0" +requires_python = ">=3.7" +summary = "Pytest plugin for measuring coverage." +dependencies = [ + "coverage[toml]>=5.2.1", + "pytest>=4.6", +] + +[[package]] +name = "pytest-integration" +version = "0.2.3" +requires_python = ">=3.6" +summary = "Organizing pytests by integration or not" + +[[package]] +name = "pytest-mock" +version = "3.11.1" +requires_python = ">=3.7" +summary = "Thin-wrapper around the mock package for easier use with pytest" +dependencies = [ + "pytest>=5.0", +] + +[[package]] +name = "pytest-xdist" +version = "3.3.1" +requires_python = ">=3.7" +summary = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" +dependencies = [ + "execnet>=1.1", + "pytest>=6.2.0", +] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +summary = "Extensions to the standard Python datetime module" +dependencies = [ + "six>=1.5", +] + +[[package]] +name = "python-dotenv" +version = "0.21.1" +requires_python = ">=3.7" +summary = "Read key-value pairs from a .env file and set them as environment variables" + +[[package]] +name = "python-jose" +version = "3.3.0" +summary = "JOSE implementation in Python" +dependencies = [ + "ecdsa!=0.15", + "pyasn1", + "rsa", +] + +[[package]] +name = "python-multipart" +version = "0.0.6" +requires_python = ">=3.7" +summary = "A streaming multipart parser for Python" + +[[package]] +name = "pyyaml" +version = "6.0.1" +requires_python = ">=3.6" +summary = "YAML parser and emitter for Python" + +[[package]] +name = "redis" +version = "4.6.0" +requires_python = ">=3.7" +summary = "Python client for Redis database and key-value store" +dependencies = [ + "async-timeout>=4.0.2; python_full_version <= \"3.11.2\"", +] + +[[package]] +name = "requests" +version = "2.29.0" +requires_python = ">=3.7" +summary = "Python HTTP for Humans." +dependencies = [ + "certifi>=2017.4.17", + "charset-normalizer<4,>=2", + "idna<4,>=2.5", + "urllib3<1.27,>=1.21.1", +] + +[[package]] +name = "requests-mock" +version = "1.11.0" +summary = "Mock out responses from the requests package" +dependencies = [ + "requests<3,>=2.3", + "six", +] + +[[package]] +name = "rich" +version = "13.5.2" +requires_python = ">=3.7.0" +summary = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +dependencies = [ + "markdown-it-py>=2.2.0", + "pygments<3.0.0,>=2.13.0", + "typing-extensions<5.0,>=4.0.0; python_version < \"3.9\"", +] + +[[package]] +name = "rsa" +version = "4.9" +requires_python = ">=3.6,<4" +summary = "Pure-Python RSA implementation" +dependencies = [ + "pyasn1>=0.1.3", +] + +[[package]] +name = "setuptools" +version = "68.1.0" +requires_python = ">=3.8" +summary = "Easily download, build, install, upgrade, and uninstall Python packages" + +[[package]] +name = "six" +version = "1.16.0" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +summary = "Python 2 and 3 compatibility utilities" + +[[package]] +name = "sniffio" +version = "1.3.0" +requires_python = ">=3.7" +summary = "Sniff out which async library your code is running under" + +[[package]] +name = "sqlalchemy" +version = "1.4.41" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +summary = "Database Abstraction Library" +dependencies = [ + "greenlet!=0.4.17; python_version >= \"3\" and (platform_machine == \"aarch64\" or (platform_machine == \"ppc64le\" or (platform_machine == \"x86_64\" or (platform_machine == \"amd64\" or (platform_machine == \"AMD64\" or (platform_machine == \"win32\" or platform_machine == \"WIN32\"))))))", +] + +[[package]] +name = "sqlalchemy-utils" +version = "0.41.1" +requires_python = ">=3.6" +summary = "Various utility functions for SQLAlchemy." +dependencies = [ + "SQLAlchemy>=1.3", +] + +[[package]] +name = "sqlalchemy2-stubs" +version = "0.0.2a35" +requires_python = ">=3.6" +summary = "Typing Stubs for SQLAlchemy 1.4" +dependencies = [ + "typing-extensions>=3.7.4", +] + +[[package]] +name = "sqlmodel" +version = "0.0.8" +requires_python = ">=3.6.1,<4.0.0" +summary = "SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness." +dependencies = [ + "SQLAlchemy<=1.4.41,>=1.4.17", + "pydantic<2.0.0,>=1.8.2", + "sqlalchemy2-stubs", +] + +[[package]] +name = "sqlparse" +version = "0.4.4" +requires_python = ">=3.5" +summary = "A non-validating SQL parser." + +[[package]] +name = "sse-starlette" +version = "1.6.5" +requires_python = ">=3.8" +summary = "\"SSE plugin for Starlette\"" +dependencies = [ + "starlette", +] + +[[package]] +name = "starlette" +version = "0.19.1" +requires_python = ">=3.6" +summary = "The little ASGI library that shines." +dependencies = [ + "anyio<5,>=3.4.0", + "typing-extensions>=3.10.0; python_version < \"3.10\"", +] + +[[package]] +name = "strawberry-graphql" +version = "0.204.0" +requires_python = ">=3.8,<4.0" +summary = "A library for creating GraphQL APIs" +dependencies = [ + "astunparse<2.0.0,>=1.6.3; python_version < \"3.9\"", + "graphql-core<3.3.0,>=3.2.0", + "python-dateutil<3.0.0,>=2.7.0", + "typing-extensions>=4.5.0", +] + +[[package]] +name = "tomli" +version = "2.0.1" +requires_python = ">=3.7" +summary = "A lil' TOML parser" + +[[package]] +name = "tomlkit" +version = "0.12.1" +requires_python = ">=3.7" +summary = "Style preserving TOML library" + +[[package]] +name = "types-cachetools" +version = "5.3.0.6" +summary = "Typing stubs for cachetools" + +[[package]] +name = "typing-extensions" +version = "4.7.1" +requires_python = ">=3.7" +summary = "Backported and Experimental Type Hints for Python 3.7+" + +[[package]] +name = "tzdata" +version = "2023.3" +requires_python = ">=2" +summary = "Provider of IANA time zone data" + +[[package]] +name = "urllib3" +version = "1.26.16" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +summary = "HTTP library with thread-safe connection pooling, file post, and more." + +[[package]] +name = "uvicorn" +version = "0.23.2" +requires_python = ">=3.8" +summary = "The lightning-fast ASGI server." +dependencies = [ + "click>=7.0", + "h11>=0.8", + "typing-extensions>=4.0; python_version < \"3.11\"", +] + +[[package]] +name = "uvicorn" +version = "0.23.2" +extras = ["standard"] +requires_python = ">=3.8" +summary = "The lightning-fast ASGI server." +dependencies = [ + "colorama>=0.4; sys_platform == \"win32\"", + "httptools>=0.5.0", + "python-dotenv>=0.13", + "pyyaml>=5.1", + "uvicorn==0.23.2", + "uvloop!=0.15.0,!=0.15.1,>=0.14.0; sys_platform != \"win32\" and (sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\")", + "watchfiles>=0.13", + "websockets>=10.4", +] + +[[package]] +name = "uvloop" +version = "0.17.0" +requires_python = ">=3.7" +summary = "Fast implementation of asyncio event loop on top of libuv" + +[[package]] +name = "vine" +version = "5.0.0" +requires_python = ">=3.6" +summary = "Promises, promises, promises." + +[[package]] +name = "virtualenv" +version = "20.24.3" +requires_python = ">=3.7" +summary = "Virtual Python Environment builder" +dependencies = [ + "distlib<1,>=0.3.7", + "filelock<4,>=3.12.2", + "platformdirs<4,>=3.9.1", +] + +[[package]] +name = "watchfiles" +version = "0.19.0" +requires_python = ">=3.7" +summary = "Simple, modern and high performance file watching and code reload in python." +dependencies = [ + "anyio>=3.0.0", +] + +[[package]] +name = "wcwidth" +version = "0.2.6" +summary = "Measures the displayed width of unicode strings in a terminal" + +[[package]] +name = "websockets" +version = "11.0.3" +requires_python = ">=3.7" +summary = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" + +[[package]] +name = "wheel" +version = "0.41.1" +requires_python = ">=3.7" +summary = "A built-package format for Python" + +[[package]] +name = "wrapt" +version = "1.15.0" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +summary = "Module for decorators, wrappers and monkey patching." + +[[package]] +name = "yarl" +version = "1.9.2" +requires_python = ">=3.7" +summary = "Yet another URL library" +dependencies = [ + "idna>=2.0", + "multidict>=4.0", +] + +[[package]] +name = "zipp" +version = "3.16.2" +requires_python = ">=3.8" +summary = "Backport of pathlib-compatible object wrapper for zip files" + +[metadata] +lock_version = "4.2" +cross_platform = true +groups = ["default", "test", "uvicorn"] +content_hash = "sha256:986ff40dfe2ccfd8f58a584f993f50dc0abb5e14f793cc57da680855c2841caa" + +[metadata.files] +"accept-types 0.4.1" = [ + {url = "https://files.pythonhosted.org/packages/08/24/559c0f1959134a0e7615a0ece082734209b1942365ec479cbe3bcca9098b/accept_types-0.4.1-py3-none-any.whl", hash = "sha256:c87feccdffb66b02f9343ff387d7fd5c451ccb2e1221fbd37ea0cedef5cf290f"}, + {url = "https://files.pythonhosted.org/packages/a3/84/6f51d94019411892c9f7fa9d461d4cef06beb35d54cd9944ea19728c4d45/accept-types-0.4.1.tar.gz", hash = "sha256:fb27099716d8f0360408c8ca86d69dbfed44455834b70d1506250abe521b535a"}, +] +"alembic 1.11.2" = [ + {url = "https://files.pythonhosted.org/packages/34/fe/eebb260c86c71d9ed861aa1434fc50601df657425b18329994af8c0bd789/alembic-1.11.2-py3-none-any.whl", hash = "sha256:7981ab0c4fad4fe1be0cf183aae17689fe394ff874fd2464adb774396faf0796"}, + {url = "https://files.pythonhosted.org/packages/88/bc/f1b6399b857a6b7af20b48d6773cc9ab3b7ee7e078bcf12787a070511ec6/alembic-1.11.2.tar.gz", hash = "sha256:678f662130dc540dac12de0ea73de9f89caea9dbea138f60ef6263149bf84657"}, +] +"amqp 5.1.1" = [ + {url = "https://files.pythonhosted.org/packages/cb/e7/bcaab89065e17915a28247fa5d4f582ca107b4544e2b1aba92d32f794a0f/amqp-5.1.1.tar.gz", hash = "sha256:2c1b13fecc0893e946c65cbd5f36427861cffa4ea2201d8f6fca22e2a373b5e2"}, + {url = "https://files.pythonhosted.org/packages/de/a3/e7b3b9d34239bae066df135060e225929d639731050c920fdc740d6b7897/amqp-5.1.1-py3-none-any.whl", hash = "sha256:6f0956d2c23d8fa6e7691934d8c3930eadb44972cbbd1a7ae3a520f735d43359"}, +] +"antlr4-python3-runtime 4.12.0" = [ + {url = "https://files.pythonhosted.org/packages/2c/be/14afc05b4789239a9d597a7fdb64abe5d7babb3a28563dc96d9c53c880f7/antlr4_python3_runtime-4.12.0-py3-none-any.whl", hash = "sha256:2c08f4dfbdc7dfd10f680681a96a55579c1e4f866f01d27099c9a54027923d70"}, + {url = "https://files.pythonhosted.org/packages/f5/4a/9335ed671e57bc8cc0a6ef7f17652b978f7098db56e29b9bea5eb7bf4a19/antlr4-python3-runtime-4.12.0.tar.gz", hash = "sha256:0a8b82f55032734f43ed6b60b8a48c25754721a75cd714eb1fe9ce6ed418b361"}, +] +"anyio 3.7.1" = [ + {url = "https://files.pythonhosted.org/packages/19/24/44299477fe7dcc9cb58d0a57d5a7588d6af2ff403fdd2d47a246c91a3246/anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"}, + {url = "https://files.pythonhosted.org/packages/28/99/2dfd53fd55ce9838e6ff2d4dac20ce58263798bd1a0dbe18b3a9af3fcfce/anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"}, +] +"asciidag 0.2.0" = [ + {url = "https://files.pythonhosted.org/packages/10/9b/00885406d04e6bcad4c51a5bff2a92b6d73ef322d8b30916fe3d28bb2c48/asciidag-0.2.0-py2.py3-none-any.whl", hash = "sha256:f7ea1e6a867ab4c3a2537ff03bc0f25d8fccc2d5109f9f329220ba4fbb1b3e02"}, + {url = "https://files.pythonhosted.org/packages/87/7a/2241c6cc1cd1c34b10ba8ced7cd4b9b47c6609fcb186f4bf2826a2f0b43c/asciidag-0.2.0.tar.gz", hash = "sha256:acf4df123fc222322467d9bdb2020e44b4e1af37d38129092a080c3cda54a788"}, +] +"asgiref 3.7.2" = [ + {url = "https://files.pythonhosted.org/packages/12/19/64e38c1c2cbf0da9635b7082bbdf0e89052e93329279f59759c24a10cc96/asgiref-3.7.2.tar.gz", hash = "sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed"}, + {url = "https://files.pythonhosted.org/packages/9b/80/b9051a4a07ad231558fcd8ffc89232711b4e618c15cb7a392a17384bbeef/asgiref-3.7.2-py3-none-any.whl", hash = "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e"}, +] +"astroid 2.15.6" = [ + {url = "https://files.pythonhosted.org/packages/86/6f/22a8153395e193369fd43ca9d54123e74360115df4f10902516313eb9f10/astroid-2.15.6-py3-none-any.whl", hash = "sha256:389656ca57b6108f939cf5d2f9a2a825a3be50ba9d589670f393236e0a03b91c"}, + {url = "https://files.pythonhosted.org/packages/d5/05/2c2ef3504e32a22b224614d45c590abb5be417cfed47ee63bf6089c3ae56/astroid-2.15.6.tar.gz", hash = "sha256:903f024859b7c7687d7a7f3a3f73b17301f8e42dfd9cc9df9d4418172d3e2dbd"}, +] +"astunparse 1.6.3" = [ + {url = "https://files.pythonhosted.org/packages/2b/03/13dde6512ad7b4557eb792fbcf0c653af6076b81e5941d36ec61f7ce6028/astunparse-1.6.3-py2.py3-none-any.whl", hash = "sha256:c2652417f2c8b5bb325c885ae329bdf3f86424075c4fd1a128674bc6fba4b8e8"}, + {url = "https://files.pythonhosted.org/packages/f3/af/4182184d3c338792894f34a62672919db7ca008c89abee9b564dd34d8029/astunparse-1.6.3.tar.gz", hash = "sha256:5ad93a8456f0d084c3456d059fd9a92cce667963232cbf763eac3bc5b7940872"}, +] +"async-timeout 4.0.3" = [ + {url = "https://files.pythonhosted.org/packages/87/d6/21b30a550dafea84b1b8eee21b5e23fa16d010ae006011221f33dcd8d7f8/async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, + {url = "https://files.pythonhosted.org/packages/a7/fa/e01228c2938de91d47b307831c62ab9e4001e747789d0b05baf779a6488c/async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, +] +"backports-zoneinfo 0.2.1" = [ + {url = "https://files.pythonhosted.org/packages/1a/ab/3e941e3fcf1b7d3ab3d0233194d99d6a0ed6b24f8f956fc81e47edc8c079/backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7b0a64cda4145548fed9efc10322770f929b944ce5cee6c0dfe0c87bf4c0c8c9"}, + {url = "https://files.pythonhosted.org/packages/1c/96/baaca3ad1b06d97138d42a225e4d4d27cd1586b646740f771706cd2d812c/backports.zoneinfo-0.2.1-cp37-cp37m-win32.whl", hash = "sha256:e55b384612d93be96506932a786bbcde5a2db7a9e6a4bb4bffe8b733f5b9036b"}, + {url = "https://files.pythonhosted.org/packages/28/d5/e2f3d6a52870045afd8c37b2681c47fd0b98679cd4851e349bfd7e19cfd7/backports.zoneinfo-0.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:8439c030a11780786a2002261569bdf362264f605dfa4d65090b64b05c9f79a7"}, + {url = "https://files.pythonhosted.org/packages/33/1c/9357061860f5d3a09e1877aa4cf7c004c55eec40a1036761144ef24d8a1d/backports.zoneinfo-0.2.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:da6013fd84a690242c310d77ddb8441a559e9cb3d3d59ebac9aca1a57b2e18bc"}, + {url = "https://files.pythonhosted.org/packages/4a/6d/eca004eeadcbf8bd64cc96feb9e355536147f0577420b44d80c7cac70767/backports.zoneinfo-0.2.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:8961c0f32cd0336fb8e8ead11a1f8cd99ec07145ec2931122faaac1c8f7fd987"}, + {url = "https://files.pythonhosted.org/packages/4c/7e/ed8af95bed90eeccfb4a4fe6ec424bc7a79e1aa983e54dd1d9062d9fa20b/backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5c144945a7752ca544b4b78c8c41544cdfaf9786f25fe5ffb10e838e19a27570"}, + {url = "https://files.pythonhosted.org/packages/6c/99/513f2c4dd41522eefc42feb86854f6cf3b1add9c175c14d90c070775e484/backports.zoneinfo-0.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a76b38c52400b762e48131494ba26be363491ac4f9a04c1b7e92483d169f6582"}, + {url = "https://files.pythonhosted.org/packages/74/a1/323f86a5ca5a559d452affb879512365a0473529398bfcf2d712a40ae088/backports.zoneinfo-0.2.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac"}, + {url = "https://files.pythonhosted.org/packages/78/cc/e27fd6493bbce8dbea7e6c1bc861fe3d3bc22c4f7c81f4c3befb8ff5bfaf/backports.zoneinfo-0.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6"}, + {url = "https://files.pythonhosted.org/packages/ad/85/475e514c3140937cf435954f78dedea1861aeab7662d11de232bdaa90655/backports.zoneinfo-0.2.1.tar.gz", hash = "sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2"}, + {url = "https://files.pythonhosted.org/packages/c0/34/5fdb0a3a28841d215c255be8fc60b8666257bb6632193c86fd04b63d4a31/backports.zoneinfo-0.2.1-cp38-cp38-win32.whl", hash = "sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328"}, + {url = "https://files.pythonhosted.org/packages/c1/8f/9b1b920a6a95652463143943fa3b8c000cb0b932ab463764a6f2a2416560/backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e81b76cace8eda1fca50e345242ba977f9be6ae3945af8d46326d776b4cf78d1"}, + {url = "https://files.pythonhosted.org/packages/d1/04/8f2fed9c0cb9c88442fc8d6372cb0f5738fb05a65b45e2d371fbc8a15087/backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:17746bd546106fa389c51dbea67c8b7c8f0d14b5526a579ca6ccf5ed72c526cf"}, + {url = "https://files.pythonhosted.org/packages/d4/79/249bd3c4f794741f04f1e0ff33ad3cca9b2d1f4299b73f78d0d9bc9ec8dc/backports.zoneinfo-0.2.1-cp36-cp36m-win32.whl", hash = "sha256:e8236383a20872c0cdf5a62b554b27538db7fa1bbec52429d8d106effbaeca08"}, + {url = "https://files.pythonhosted.org/packages/ef/9a/8de8f379d5b3961a517762cc051b366de3f7d4d3a2250120e7a71e25fab4/backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:89a48c0d158a3cc3f654da4c2de1ceba85263fafb861b98b59040a5086259722"}, + {url = "https://files.pythonhosted.org/packages/f9/04/33e910faffe91a5680d68a064162525779259ae5de3b0c0c5bd9c4e900e0/backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:1c5742112073a563c81f786e77514969acb58649bcdf6cdf0b4ed31a348d4546"}, +] +"bcrypt 4.0.1" = [ + {url = "https://files.pythonhosted.org/packages/13/68/f3184c1f15581ebd936125b4da04cba0995f97ecd5ee8f4262c8ebba2646/bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:705b2cea8a9ed3d55b4491887ceadb0106acf7c6387699fca771af56b1cdeeda"}, + {url = "https://files.pythonhosted.org/packages/28/ed/3c443bfbfdb37cd7c0d055b961311f49049ab4a00f45ba3bfd10d33a9443/bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b3b85202d95dd568efcb35b53936c5e3b3600c7cdcc6115ba461df3a8e89f38d"}, + {url = "https://files.pythonhosted.org/packages/2c/be/376341b47e1e3fc424c9df1af60b5aedbd5ab04f73ccdf4107e42d92ef09/bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:5ad4d32a28b80c5fa6671ccfb43676e8c1cc232887759d1cd7b6f56ea4355215"}, + {url = "https://files.pythonhosted.org/packages/41/16/49ff5146fb815742ad58cafb5034907aa7f166b1344d0ddd7fd1c818bd17/bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0eaa47d4661c326bfc9d08d16debbc4edf78778e6aaba29c1bc7ce67214d4410"}, + {url = "https://files.pythonhosted.org/packages/41/86/05248719aa42a4fe1ca379d45794198700e992b91d389bfaa69533fc3331/bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b57adba8a1444faf784394de3436233728a1ecaeb6e07e8c22c8848f179b893c"}, + {url = "https://files.pythonhosted.org/packages/46/81/d8c22cd7e5e1c6a7d48e41a1d1d46c92f17dae70a54d9814f746e6027dec/bcrypt-4.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:8a68f4341daf7522fe8d73874de8906f3a339048ba406be6ddc1b3ccb16fc0d9"}, + {url = "https://files.pythonhosted.org/packages/5e/01/098b798dc6c6984f2d5026269e80d7cad22d6ecacd5989bdf35a9c99a03d/bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:2b3ac11cf45161628f1f3733263e63194f22664bf4d0c0f3ab34099c02134665"}, + {url = "https://files.pythonhosted.org/packages/64/fe/da28a5916128d541da0993328dc5cf4b43dfbf6655f2c7a2abe26ca2dc88/bcrypt-4.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ca3204d00d3cb2dfed07f2d74a25f12fc12f73e606fcaa6975d1f7ae69cacbb2"}, + {url = "https://files.pythonhosted.org/packages/77/2c/53c17079898584306eafdc937e0c7cc1bf8e2fe17e9909716ef3f9d6555d/bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbb03eec97496166b704ed663a53680ab57c5084b2fc98ef23291987b525cb7d"}, + {url = "https://files.pythonhosted.org/packages/78/d4/3b2657bd58ef02b23a07729b0df26f21af97169dbd0b5797afa9e97ebb49/bcrypt-4.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:b1023030aec778185a6c16cf70f359cbb6e0c289fd564a7cfa29e727a1c38f8f"}, + {url = "https://files.pythonhosted.org/packages/7d/50/e683d8418974a602ba40899c8a5c38b3decaf5a4d36c32fc65dce454d8a8/bcrypt-4.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:a522427293d77e1c29e303fc282e2d71864579527a04ddcfda6d4f8396c6c36a"}, + {url = "https://files.pythonhosted.org/packages/87/69/edacb37481d360d06fc947dab5734aaf511acb7d1a1f9e2849454376c0f8/bcrypt-4.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:e9a51bbfe7e9802b5f3508687758b564069ba937748ad7b9e890086290d2f79e"}, + {url = "https://files.pythonhosted.org/packages/8c/ae/3af7d006aacf513975fd1948a6b4d6f8b4a307f8a244e1a3d3774b297aad/bcrypt-4.0.1.tar.gz", hash = "sha256:27d375903ac8261cfe4047f6709d16f7d18d39b1ec92aaf72af989552a650ebd"}, + {url = "https://files.pythonhosted.org/packages/99/a5/ff4aaf2adbefb2c9808d49cec37f65e0572c4ce856b13b194fd87a6cbd14/bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:67a97e1c405b24f19d08890e7ae0c4f7ce1e56a712a016746c8b2d7732d65d4b"}, + {url = "https://files.pythonhosted.org/packages/aa/48/fd2b197a9741fa790ba0b88a9b10b5e88e62ff5cf3e1bc96d8354d7ce613/bcrypt-4.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae88eca3024bb34bb3430f964beab71226e761f51b912de5133470b649d82344"}, + {url = "https://files.pythonhosted.org/packages/aa/ca/6a534669890725cbb8c1fb4622019be31813c8edaa7b6d5b62fc9360a17e/bcrypt-4.0.1-cp36-abi3-win32.whl", hash = "sha256:2caffdae059e06ac23fce178d31b4a702f2a3264c20bfb5ff541b338194d8fab"}, + {url = "https://files.pythonhosted.org/packages/d8/f6/43ade4d37a3319baee9aec53f636411e70c18f0e4add9cc44a18f517af5f/bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf4fa8b2ca74381bb5442c089350f09a3f17797829d958fad058d6e44d9eb83c"}, + {url = "https://files.pythonhosted.org/packages/dd/4f/3632a69ce344c1551f7c9803196b191a8181c6a1ad2362c225581ef0d383/bcrypt-4.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:089098effa1bc35dc055366740a067a2fc76987e8ec75349eb9484061c54f535"}, + {url = "https://files.pythonhosted.org/packages/ec/0a/1582790232fef6c2aa201f345577306b8bfe465c2c665dec04c86a016879/bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:08d2947c490093a11416df18043c27abe3921558d2c03e2076ccb28a116cb6d0"}, + {url = "https://files.pythonhosted.org/packages/fb/4b/e255df2000c2de4df524740b5f1d0a31157a1f7715b3eaf2e8f9c5c0acbb/bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3100851841186c25f127731b9fa11909ab7b1df6fc4b9f8353f4f1fd952fbf71"}, + {url = "https://files.pythonhosted.org/packages/fb/a7/ee4561fd9b78ca23c8e5591c150cc58626a5dfb169345ab18e1c2c664ee0/bcrypt-4.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:fbdaec13c5105f0c4e5c52614d04f0bca5f5af007910daa8b6b12095edaa67b3"}, +] +"billiard 4.1.0" = [ + {url = "https://files.pythonhosted.org/packages/b3/9c/c02d3988ddb0e32e974db1d8434616f1503b12fc7087bf18243ccd69a60a/billiard-4.1.0.tar.gz", hash = "sha256:1ad2eeae8e28053d729ba3373d34d9d6e210f6e4d8bf0a9c64f92bd053f1edf5"}, + {url = "https://files.pythonhosted.org/packages/e3/a4/ae83eeb0563804a4148ff0e52f530e675bc90718c7c770cdfe4af1340c86/billiard-4.1.0-py3-none-any.whl", hash = "sha256:0f50d6be051c6b2b75bfbc8bfd85af195c5739c281d3f5b86a5640c65563614a"}, +] +"cachelib 0.10.2" = [ + {url = "https://files.pythonhosted.org/packages/70/0b/e7647e072ff60997d69517072145ef56898278afda7deff7cc6858b1541f/cachelib-0.10.2.tar.gz", hash = "sha256:593faeee62a7c037d50fc835617a01b887503f972fb52b188ae7e50e9cb69740"}, + {url = "https://files.pythonhosted.org/packages/8c/83/449fb201dd5a6536bb022eb50ddc58da9e3d5cdf674c12df2f6dd46bd52f/cachelib-0.10.2-py3-none-any.whl", hash = "sha256:42d49f2fad9310dd946d7be73d46776bcd4d5fde4f49ad210cfdd447fbdfc346"}, +] +"cachetools 5.3.1" = [ + {url = "https://files.pythonhosted.org/packages/9d/8b/8e2ebf5ee26c21504de5ea2fb29cc6ae612b35fd05f959cdb641feb94ec4/cachetools-5.3.1.tar.gz", hash = "sha256:dce83f2d9b4e1f732a8cd44af8e8fab2dbe46201467fc98b3ef8f269092bf62b"}, + {url = "https://files.pythonhosted.org/packages/a9/c9/c8a7710f2cedcb1db9224fdd4d8307c9e48cbddc46c18b515fefc0f1abbe/cachetools-5.3.1-py3-none-any.whl", hash = "sha256:95ef631eeaea14ba2e36f06437f36463aac3a096799e876ee55e5cdccb102590"}, +] +"celery 5.3.1" = [ + {url = "https://files.pythonhosted.org/packages/18/b9/cb8d519ea0094b9b8fe7480225c14937517729f8ec927643dc7379904f64/celery-5.3.1-py3-none-any.whl", hash = "sha256:27f8f3f3b58de6e0ab4f174791383bbd7445aff0471a43e99cfd77727940753f"}, + {url = "https://files.pythonhosted.org/packages/a4/e2/102f8d3453a9f1c6918245a97b9b8e7352a2925d4c5477a7401de2bb54dc/celery-5.3.1.tar.gz", hash = "sha256:f84d1c21a1520c116c2b7d26593926581191435a03aa74b77c941b93ca1c6210"}, +] +"certifi 2023.7.22" = [ + {url = "https://files.pythonhosted.org/packages/4c/dd/2234eab22353ffc7d94e8d13177aaa050113286e93e7b40eae01fbf7c3d9/certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, + {url = "https://files.pythonhosted.org/packages/98/98/c2ff18671db109c9f10ed27f5ef610ae05b73bd876664139cf95bd1429aa/certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, +] +"cffi 1.15.1" = [ + {url = "https://files.pythonhosted.org/packages/00/05/23a265a3db411b0bfb721bf7a116c7cecaf3eb37ebd48a6ea4dfb0a3244d/cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, + {url = "https://files.pythonhosted.org/packages/03/7b/259d6e01a6083acef9d3c8c88990c97d313632bb28fa84d6ab2bb201140a/cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, + {url = "https://files.pythonhosted.org/packages/0e/65/0d7b5dad821ced4dcd43f96a362905a68ce71e6b5f5cfd2fada867840582/cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, + {url = "https://files.pythonhosted.org/packages/0e/e2/a23af3d81838c577571da4ff01b799b0c2bbde24bd924d97e228febae810/cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, + {url = "https://files.pythonhosted.org/packages/10/72/617ee266192223a38b67149c830bd9376b69cf3551e1477abc72ff23ef8e/cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, + {url = "https://files.pythonhosted.org/packages/18/8f/5ff70c7458d61fa8a9752e5ee9c9984c601b0060aae0c619316a1e1f1ee5/cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, + {url = "https://files.pythonhosted.org/packages/1d/76/bcebbbab689f5f6fc8a91e361038a3001ee2e48c5f9dbad0a3b64a64cc9e/cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, + {url = "https://files.pythonhosted.org/packages/22/c6/df826563f55f7e9dd9a1d3617866282afa969fe0d57decffa1911f416ed8/cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, + {url = "https://files.pythonhosted.org/packages/23/8b/2e8c2469eaf89f7273ac685164949a7e644cdfe5daf1c036564208c3d26b/cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, + {url = "https://files.pythonhosted.org/packages/2b/a8/050ab4f0c3d4c1b8aaa805f70e26e84d0e27004907c5b8ecc1d31815f92a/cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, + {url = "https://files.pythonhosted.org/packages/2d/86/3ca57cddfa0419f6a95d1c8478f8f622ba597e3581fd501bbb915b20eb75/cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, + {url = "https://files.pythonhosted.org/packages/2e/7a/68c35c151e5b7a12650ecc12fdfb85211aa1da43e9924598451c4a0a3839/cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, + {url = "https://files.pythonhosted.org/packages/32/2a/63cb8c07d151de92ff9d897b2eb27ba6a0e78dda8e4c5f70d7b8c16cd6a2/cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, + {url = "https://files.pythonhosted.org/packages/32/bd/d0809593f7976828f06a492716fbcbbfb62798bbf60ea1f65200b8d49901/cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, + {url = "https://files.pythonhosted.org/packages/37/5a/c37631a86be838bdd84cc0259130942bf7e6e32f70f4cab95f479847fb91/cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, + {url = "https://files.pythonhosted.org/packages/3a/12/d6066828014b9ccb2bbb8e1d9dc28872d20669b65aeb4a86806a0757813f/cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, + {url = "https://files.pythonhosted.org/packages/3a/75/a162315adeaf47e94a3b7f886a8e31d77b9e525a387eef2d6f0efc96a7c8/cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, + {url = "https://files.pythonhosted.org/packages/3f/fa/dfc242febbff049509e5a35a065bdc10f90d8c8585361c2c66b9c2f97a01/cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, + {url = "https://files.pythonhosted.org/packages/43/a0/cc7370ef72b6ee586369bacd3961089ab3d94ae712febf07a244f1448ffd/cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, + {url = "https://files.pythonhosted.org/packages/47/51/3049834f07cd89aceef27f9c56f5394ca6725ae6a15cff5fbdb2f06a24ad/cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, + {url = "https://files.pythonhosted.org/packages/47/97/137f0e3d2304df2060abb872a5830af809d7559a5a4b6a295afb02728e65/cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, + {url = "https://files.pythonhosted.org/packages/50/34/4cc590ad600869502c9838b4824982c122179089ed6791a8b1c95f0ff55e/cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, + {url = "https://files.pythonhosted.org/packages/5b/1a/e1ee5bed11d8b6540c05a8e3c32448832d775364d4461dd6497374533401/cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, + {url = "https://files.pythonhosted.org/packages/5d/4e/4e0bb5579b01fdbfd4388bd1eb9394a989e1336203a4b7f700d887b233c1/cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, + {url = "https://files.pythonhosted.org/packages/5d/6f/3a2e167113eabd46ed300ff3a6a1e9277a3ad8b020c4c682f83e9326fcf7/cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, + {url = "https://files.pythonhosted.org/packages/69/bf/335f8d95510b1a26d7c5220164dc739293a71d5540ecd54a2f66bac3ecb8/cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, + {url = "https://files.pythonhosted.org/packages/71/d7/0fe0d91b0bbf610fb7254bb164fa8931596e660d62e90fb6289b7ee27b09/cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, + {url = "https://files.pythonhosted.org/packages/77/b7/d3618d612be01e184033eab90006f8ca5b5edafd17bf247439ea4e167d8a/cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, + {url = "https://files.pythonhosted.org/packages/79/4b/33494eb0adbcd884656c48f6db0c98ad8a5c678fb8fb5ed41ab546b04d8c/cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, + {url = "https://files.pythonhosted.org/packages/7c/3e/5d823e5bbe00285e479034bcad44177b7353ec9fdcd7795baac5ccf82950/cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, + {url = "https://files.pythonhosted.org/packages/85/1f/a3c533f8d377da5ca7edb4f580cc3edc1edbebc45fac8bb3ae60f1176629/cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, + {url = "https://files.pythonhosted.org/packages/87/4b/64e8bd9d15d6b22b6cb11997094fbe61edf453ea0a97c8675cb7d1c3f06f/cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, + {url = "https://files.pythonhosted.org/packages/87/ee/ddc23981fc0f5e7b5356e98884226bcb899f95ebaefc3e8e8b8742dd7e22/cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, + {url = "https://files.pythonhosted.org/packages/88/89/c34caf63029fb7628ec2ebd5c88ae0c9bd17db98c812e4065a4d020ca41f/cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, + {url = "https://files.pythonhosted.org/packages/91/bc/b7723c2fe7a22eee71d7edf2102cd43423d5f95ff3932ebaa2f82c7ec8d0/cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, + {url = "https://files.pythonhosted.org/packages/93/d0/2e2b27ea2f69b0ec9e481647822f8f77f5fc23faca2dd00d1ff009940eb7/cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, + {url = "https://files.pythonhosted.org/packages/9f/52/1e2b43cfdd7d9a39f48bc89fcaee8d8685b1295e205a4f1044909ac14d89/cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, + {url = "https://files.pythonhosted.org/packages/a4/42/54bdf22cf6c8f95113af645d0bd7be7f9358ea5c2d57d634bb11c6b4d0b2/cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, + {url = "https://files.pythonhosted.org/packages/a8/16/06b84a7063a4c0a2b081030fdd976022086da9c14e80a9ed4ba0183a98a9/cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, + {url = "https://files.pythonhosted.org/packages/a9/ba/e082df21ebaa9cb29f2c4e1d7e49a29b90fcd667d43632c6674a16d65382/cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, + {url = "https://files.pythonhosted.org/packages/aa/02/ab15b3aa572759df752491d5fa0f74128cd14e002e8e3257c1ab1587810b/cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, + {url = "https://files.pythonhosted.org/packages/ad/26/7b3a73ab7d82a64664c7c4ea470e4ec4a3c73bb4f02575c543a41e272de5/cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, + {url = "https://files.pythonhosted.org/packages/af/cb/53b7bba75a18372d57113ba934b27d0734206c283c1dfcc172347fbd9f76/cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, + {url = "https://files.pythonhosted.org/packages/af/da/9441d56d7dd19d07dcc40a2a5031a1f51c82a27cee3705edf53dadcac398/cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, + {url = "https://files.pythonhosted.org/packages/b3/b8/89509b6357ded0cbacc4e430b21a4ea2c82c2cdeb4391c148b7c7b213bed/cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, + {url = "https://files.pythonhosted.org/packages/b5/7d/df6c088ef30e78a78b0c9cca6b904d5abb698afb5bc8f5191d529d83d667/cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, + {url = "https://files.pythonhosted.org/packages/b5/80/ce5ba093c2475a73df530f643a61e2969a53366e372b24a32f08cd10172b/cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, + {url = "https://files.pythonhosted.org/packages/b7/8b/06f30caa03b5b3ac006de4f93478dbd0239e2a16566d81a106c322dc4f79/cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, + {url = "https://files.pythonhosted.org/packages/b9/4a/dde4d093a3084d0b0eadfb2703f71e31a5ced101a42c839ac5bbbd1710f2/cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, + {url = "https://files.pythonhosted.org/packages/c1/25/16a082701378170559bb1d0e9ef2d293cece8dc62913d79351beb34c5ddf/cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, + {url = "https://files.pythonhosted.org/packages/c2/0b/3b09a755ddb977c167e6d209a7536f6ade43bb0654bad42e08df1406b8e4/cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, + {url = "https://files.pythonhosted.org/packages/c5/ff/3f9d73d480567a609e98beb0c64359f8e4f31cb6a407685da73e5347b067/cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, + {url = "https://files.pythonhosted.org/packages/c6/3d/dd085bb831b22ce4d0b7ba8550e6d78960f02f770bbd1314fea3580727f8/cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, + {url = "https://files.pythonhosted.org/packages/c9/e3/0a52838832408cfbbf3a59cb19bcd17e64eb33795c9710ca7d29ae10b5b7/cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, + {url = "https://files.pythonhosted.org/packages/d3/56/3e94aa719ae96eeda8b68b3ec6e347e0a23168c6841dc276ccdcdadc9f32/cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, + {url = "https://files.pythonhosted.org/packages/d3/e1/e55ca2e0dd446caa2cc8f73c2b98879c04a1f4064ac529e1836683ca58b8/cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, + {url = "https://files.pythonhosted.org/packages/da/ff/ab939e2c7b3f40d851c0f7192c876f1910f3442080c9c846532993ec3cef/cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, + {url = "https://files.pythonhosted.org/packages/df/02/aef53d4aa43154b829e9707c8c60bab413cd21819c4a36b0d7aaa83e2a61/cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, + {url = "https://files.pythonhosted.org/packages/e8/ff/c4b7a358526f231efa46a375c959506c87622fb4a2c5726e827c55e6adf2/cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, + {url = "https://files.pythonhosted.org/packages/ea/be/c4ad40ad441ac847b67c7a37284ae3c58f39f3e638c6b0f85fb662233825/cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, + {url = "https://files.pythonhosted.org/packages/ed/a3/c5f01988ddb70a187c3e6112152e01696188c9f8a4fa4c68aa330adbb179/cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, + {url = "https://files.pythonhosted.org/packages/ef/41/19da352d341963d29a33bdb28433ba94c05672fb16155f794fad3fd907b0/cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, + {url = "https://files.pythonhosted.org/packages/f9/96/fc9e118c47b7adc45a0676f413b4a47554e5f3b6c99b8607ec9726466ef1/cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, + {url = "https://files.pythonhosted.org/packages/ff/fe/ac46ca7b00e9e4f9c62e7928a11bc9227c86e2ff43526beee00cdfb4f0e8/cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, +] +"cfgv 3.4.0" = [ + {url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, + {url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, +] +"charset-normalizer 3.2.0" = [ + {url = "https://files.pythonhosted.org/packages/08/f7/3f36bb1d0d74846155c7e3bf1477004c41243bb510f9082e785809787735/charset_normalizer-3.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c"}, + {url = "https://files.pythonhosted.org/packages/09/79/1b7af063e7c57a51aab7f2aaccd79bb8a694dfae668e8aa79b0b045b17bc/charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c"}, + {url = "https://files.pythonhosted.org/packages/0d/dd/e598cc4e4052aa0779d4c6d5e9840d21ed238834944ccfbc6b33f792c426/charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c"}, + {url = "https://files.pythonhosted.org/packages/0f/16/8d50877a7215d31f024245a0acbda9e484dd70a21794f3109a6d8eaeba99/charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2"}, + {url = "https://files.pythonhosted.org/packages/13/de/10c14aa51375b90ed62232935e6c8997756178e6972c7695cdf0500a60ad/charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"}, + {url = "https://files.pythonhosted.org/packages/16/36/72dcb89fbd0ff89c556ed4a2cc79fc1b262dcc95e9082d8a5911744dadc9/charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592"}, + {url = "https://files.pythonhosted.org/packages/19/9f/552f15cb1dade9332d6f0208fa3e6c21bb3eecf1c89862413ed8a3c75900/charset_normalizer-3.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959"}, + {url = "https://files.pythonhosted.org/packages/1b/2c/7376d101efdec15e61e9861890cf107c6ce3cceba89eb87cc416ee0528cd/charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e"}, + {url = "https://files.pythonhosted.org/packages/23/59/8011a01cd8b904d08d86b4a49f407e713d20ee34155300dc698892a29f8b/charset_normalizer-3.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252"}, + {url = "https://files.pythonhosted.org/packages/27/19/49de2049561eca73233ba0ed7a843c184d364ef3b8886969a48d6793c830/charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23"}, + {url = "https://files.pythonhosted.org/packages/28/ec/cda85baa366071c48593774eb59a5031793dd974fa26f4982829e971df6b/charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918"}, + {url = "https://files.pythonhosted.org/packages/2a/53/cf0a48de1bdcf6ff6e1c9a023f5f523dfe303e4024f216feac64b6eb7f67/charset-normalizer-3.2.0.tar.gz", hash = "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace"}, + {url = "https://files.pythonhosted.org/packages/2e/29/dc806e009ddb357371458de3e93cfde78ea6e5c995df008fb6b048769457/charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a"}, + {url = "https://files.pythonhosted.org/packages/2e/56/faee2b51d73e9675b4766366d925f17c253797e5839c28e1c720ec9dfbfc/charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f"}, + {url = "https://files.pythonhosted.org/packages/31/e9/ae16eca3cf24a15ebfb1e36d755c884a91d61ed40de5e612de6555827729/charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3"}, + {url = "https://files.pythonhosted.org/packages/3d/91/47454b64516f83c5affdcdb0398bff540185d2c37b687410d67507006624/charset_normalizer-3.2.0-cp38-cp38-win32.whl", hash = "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706"}, + {url = "https://files.pythonhosted.org/packages/45/60/1b2113fe172ac66ac4d210034e937ebe0be30bcae9a7a4d2ae5ad3c018b3/charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8"}, + {url = "https://files.pythonhosted.org/packages/47/03/2cde6c5fba0115e8726272aabfca33b9d84d377cc11c4bab092fa9617d7a/charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5"}, + {url = "https://files.pythonhosted.org/packages/47/71/2ce8dca3e8cf1f65c36b6317cf68382bb259966e3a208da6e5550029ab79/charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10"}, + {url = "https://files.pythonhosted.org/packages/49/60/87a026215ed77184c413ebb85bafa6c0a998bdc0d1e03b894fa326f2b0f9/charset_normalizer-3.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037"}, + {url = "https://files.pythonhosted.org/packages/4a/46/a22af93e707f0d3c3865a2c21b4363c778239f5a6405aadd220992ac3058/charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952"}, + {url = "https://files.pythonhosted.org/packages/4d/ce/8ce85a7d61bbfb5e49094040642f1558b3cf6cf2ad91bbb3616a967dea38/charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7"}, + {url = "https://files.pythonhosted.org/packages/59/8e/62651b09599938e5e6d068ea723fd22d3f8c14d773c3c11c58e5e7d1eab7/charset_normalizer-3.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3"}, + {url = "https://files.pythonhosted.org/packages/5a/60/eeb158f11b0dee921d3e44bf37971271060b234ee60b14fa16ccc1947cbe/charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c"}, + {url = "https://files.pythonhosted.org/packages/5c/f2/f3faa20684729d3910af2ee142e30432c7a46a817eadeeab87366ed87bbb/charset_normalizer-3.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea"}, + {url = "https://files.pythonhosted.org/packages/5d/28/f69dac79bf3986a52bc2f7dc561360c2c9c88cb0270738d86ee5a3d8a0ba/charset_normalizer-3.2.0-cp37-cp37m-win32.whl", hash = "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1"}, + {url = "https://files.pythonhosted.org/packages/5f/52/e8ca03368aeecdd5c0057bd1f8ef189796d232b152e3de4244bb5a72d135/charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3"}, + {url = "https://files.pythonhosted.org/packages/63/f9/14ffa4b88c1b42837dfa488b0943b7bd7f54f5b63135bf97e5001f6957e7/charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4"}, + {url = "https://files.pythonhosted.org/packages/6b/b2/9d0c8fe83572a37bd66150399e289d8e96d62eca359ffa67c021b4120887/charset_normalizer-3.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e"}, + {url = "https://files.pythonhosted.org/packages/6b/b7/f042568ee89c378b457f73fda1642fd3b795df79c285520e4ec8a74c8b09/charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3"}, + {url = "https://files.pythonhosted.org/packages/6f/14/8e317fa69483a2823ea358a77e243c37f23f536a7add1b605460269593b5/charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa"}, + {url = "https://files.pythonhosted.org/packages/79/55/9aef5046a1765acacf28f80994f5a964ab4f43ab75208b1265191a11004b/charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329"}, + {url = "https://files.pythonhosted.org/packages/7b/c6/7f75892d87d7afcf8ed909f3e74de1bc61abd9d77cd9aab1f449430856c5/charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f"}, + {url = "https://files.pythonhosted.org/packages/80/75/eadff07a61d5602b6b19859d464bc0983654ae79114ef8aa15797b02271c/charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4"}, + {url = "https://files.pythonhosted.org/packages/81/a0/96317ce912b512b7998434eae5e24b28bcc5f1680ad85348e31e1ca56332/charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed"}, + {url = "https://files.pythonhosted.org/packages/85/52/77ab28e0eb07f12a02732c55abfc3be481bd46c91d5ade76a8904dfb59a4/charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22"}, + {url = "https://files.pythonhosted.org/packages/89/f5/88e9dd454756fea555198ddbe6fa40d6408ec4f10ad4f0a911e0b7e471e4/charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449"}, + {url = "https://files.pythonhosted.org/packages/8b/b4/e6da7d4c044852d7a08ba945868eaefa32e8c43665e746f420ef14bdb130/charset_normalizer-3.2.0-cp39-cp39-win32.whl", hash = "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9"}, + {url = "https://files.pythonhosted.org/packages/8b/c4/62b920ec8f4ec7b55cd29db894ced9a649214fd506295ac19fb786fe3c6f/charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f"}, + {url = "https://files.pythonhosted.org/packages/8e/a2/77cf1f042a4697822070fd5f3f5f58fd0e3ee798d040e3863eac43e3a2e5/charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09"}, + {url = "https://files.pythonhosted.org/packages/91/6e/db0e545302bf93b6dbbdc496dd192c7f8e8c3bb1584acba069256d8b51d4/charset_normalizer-3.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489"}, + {url = "https://files.pythonhosted.org/packages/91/e6/8fa919fc84a106e9b04109de62bdf8526899e2754a64da66e1cd50ac1faa/charset_normalizer-3.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac"}, + {url = "https://files.pythonhosted.org/packages/94/fc/53e12f67fff7a127fe2998de3469a9856c6c7cf67f18dc5f417df3e5e60f/charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd"}, + {url = "https://files.pythonhosted.org/packages/95/d2/6f25fddfbe31448ceea236e03b70d2bbd647d4bc9148bf9665307794c4f2/charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd"}, + {url = "https://files.pythonhosted.org/packages/95/d3/ed29b2d14ec9044a223dcf7c439fa550ef9c6d06c9372cd332374d990559/charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299"}, + {url = "https://files.pythonhosted.org/packages/95/ee/8bb03c3518a228dc5956d1b4f46d8258639ff118881fba456b72b06561cf/charset_normalizer-3.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346"}, + {url = "https://files.pythonhosted.org/packages/97/f6/0bae7bdfb07ca42bf5e3e37dbd0cce02d87dd6e87ea85dff43106dfc1f48/charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669"}, + {url = "https://files.pythonhosted.org/packages/99/23/7262c6a7c8a8c2ec783886166a432985915f67277bc44020d181e5c04584/charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6"}, + {url = "https://files.pythonhosted.org/packages/9c/71/bf12b8e0d6e1d84ed29c3e16ea1efc47ae96487bde823130d12139c434a0/charset_normalizer-3.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149"}, + {url = "https://files.pythonhosted.org/packages/9c/74/10a518cd27c2c595768f70ddbd7d05c9acb01b26033f79433105ccc73308/charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982"}, + {url = "https://files.pythonhosted.org/packages/a1/5c/c4ae954751f285c6170c3ef4de04492f88ddb29d218fefbdcbd9fb32ba5c/charset_normalizer-3.2.0-cp311-cp311-win32.whl", hash = "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1"}, + {url = "https://files.pythonhosted.org/packages/a4/65/057bf29660aae6ade0816457f8db4e749e5c0bfa2366eb5f67db9912fa4c/charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad"}, + {url = "https://files.pythonhosted.org/packages/ad/0d/9aa61083c35dc21e75a97c0ee53619daf0e5b4fd3b8b4d8bb5e7e56ed302/charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94"}, + {url = "https://files.pythonhosted.org/packages/af/3d/57e7e401f8db6dd0c56e366d69dc7366173fc549bcd533dea15f2a805000/charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a"}, + {url = "https://files.pythonhosted.org/packages/af/6f/b9b1613a5b672004f08ef3c02242b07406ff36164725ff15207737601de5/charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710"}, + {url = "https://files.pythonhosted.org/packages/b6/2a/03e909cad170b0df5ce8b731fecbc872b7b922a1d38da441b5062a89e53f/charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2"}, + {url = "https://files.pythonhosted.org/packages/bc/85/ef25d4ba14c7653c3020a1c6e1a7413e6791ef36a0ac177efa605fc2c737/charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6"}, + {url = "https://files.pythonhosted.org/packages/bf/a0/188f223c7d8b924fb9b554b9d27e0e7506fd5bf9cfb6dbacb2dfd5832b53/charset_normalizer-3.2.0-py3-none-any.whl", hash = "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6"}, + {url = "https://files.pythonhosted.org/packages/c1/92/4e30c977d2dc49ca7f84a053ccefd86097a9d1a220f3e1d1f9932561a992/charset_normalizer-3.2.0-cp310-cp310-win32.whl", hash = "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96"}, + {url = "https://files.pythonhosted.org/packages/cb/dd/dce14328e6abe0f475e606131298b4c8f628abd62a4e6f27fdfa496b9efe/charset_normalizer-3.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80"}, + {url = "https://files.pythonhosted.org/packages/cb/e7/5e43745003bf1f90668c7be23fc5952b3a2b9c2558f16749411c18039b36/charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a"}, + {url = "https://files.pythonhosted.org/packages/cb/f9/a652e1b495345000bb7f0e2a960a82ca941db55cb6de158d542918f8b52b/charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2"}, + {url = "https://files.pythonhosted.org/packages/d3/d8/50a33f82bdf25e71222a55cef146310e3e9fe7d5790be5281d715c012eae/charset_normalizer-3.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858"}, + {url = "https://files.pythonhosted.org/packages/e8/74/077cb06aed5d41118a5803e842943311032ab2fb94cf523be620c5be9911/charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029"}, + {url = "https://files.pythonhosted.org/packages/e8/ad/ac491a1cf960ec5873c1b0e4fd4b90b66bfed4a1063933612f2da8189eb8/charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d"}, + {url = "https://files.pythonhosted.org/packages/ec/a7/96835706283d63fefbbbb4f119d52f195af00fc747e67cc54397c56312c8/charset_normalizer-3.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9"}, + {url = "https://files.pythonhosted.org/packages/ed/21/03b4a3533b7a845ee31ed4542ca06debdcf7f12c099ae3dd6773c275b0df/charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c"}, + {url = "https://files.pythonhosted.org/packages/ee/ff/997d61ca61efe90662181f494c8e9fdac14e32de26cc6cb7c7a3fe96c862/charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a"}, + {url = "https://files.pythonhosted.org/packages/f0/24/7e6c604d80a8eb4378cb075647e65b7905f06645243b43c79fe4b7487ed7/charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623"}, + {url = "https://files.pythonhosted.org/packages/f1/f2/ef1479e741a7ed166b8253987071b2cf2d2b727fc8fa081520e3f7c97e44/charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a"}, + {url = "https://files.pythonhosted.org/packages/f2/e8/d9651a0afd4ee792207b24bd1d438ed750f1c0f29df62bd73d24ded428f9/charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020"}, + {url = "https://files.pythonhosted.org/packages/f4/39/b024eb6c2a2b8136f1f48fd2f2eee22ed98fbfe3cd7ddf81dad2b8dd3c1b/charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46"}, + {url = "https://files.pythonhosted.org/packages/f5/50/410da81fd67eb1becef9d633f6aae9f6e296f60126cfc3d19631f7919f76/charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a"}, + {url = "https://files.pythonhosted.org/packages/f9/0d/514be8597d7a96243e5467a37d337b9399cec117a513fcf9328405d911c0/charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200"}, + {url = "https://files.pythonhosted.org/packages/fd/17/0a1dba835ec37a3cc025f5c49653effb23f8cd391dea5e60a5696d639a92/charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d"}, +] +"click 8.1.6" = [ + {url = "https://files.pythonhosted.org/packages/1a/70/e63223f8116931d365993d4a6b7ef653a4d920b41d03de7c59499962821f/click-8.1.6-py3-none-any.whl", hash = "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5"}, + {url = "https://files.pythonhosted.org/packages/72/bd/fedc277e7351917b6c4e0ac751853a97af261278a4c7808babafa8ef2120/click-8.1.6.tar.gz", hash = "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd"}, +] +"click-didyoumean 0.3.0" = [ + {url = "https://files.pythonhosted.org/packages/2f/a7/822fbc659be70dcb75a91fb91fec718b653326697d0e9907f4f90114b34f/click-didyoumean-0.3.0.tar.gz", hash = "sha256:f184f0d851d96b6d29297354ed981b7dd71df7ff500d82fa6d11f0856bee8035"}, + {url = "https://files.pythonhosted.org/packages/ad/36/4599267417fc78b587b1588e0647a468c60b36c02bb723d450d050738fa8/click_didyoumean-0.3.0-py3-none-any.whl", hash = "sha256:a0713dc7a1de3f06bc0df5a9567ad19ead2d3d5689b434768a6145bff77c0667"}, +] +"click-plugins 1.1.1" = [ + {url = "https://files.pythonhosted.org/packages/5f/1d/45434f64ed749540af821fd7e42b8e4d23ac04b1eda7c26613288d6cd8a8/click-plugins-1.1.1.tar.gz", hash = "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b"}, + {url = "https://files.pythonhosted.org/packages/e9/da/824b92d9942f4e472702488857914bdd50f73021efea15b4cad9aca8ecef/click_plugins-1.1.1-py2.py3-none-any.whl", hash = "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8"}, +] +"click-repl 0.3.0" = [ + {url = "https://files.pythonhosted.org/packages/52/40/9d857001228658f0d59e97ebd4c346fe73e138c6de1bce61dc568a57c7f8/click_repl-0.3.0-py3-none-any.whl", hash = "sha256:fb7e06deb8da8de86180a33a9da97ac316751c094c6899382da7feeeeb51b812"}, + {url = "https://files.pythonhosted.org/packages/cb/a2/57f4ac79838cfae6912f997b4d1a64a858fb0c86d7fcaae6f7b58d267fca/click-repl-0.3.0.tar.gz", hash = "sha256:17849c23dba3d667247dc4defe1757fff98694e90fe37474f3feebb69ced26a9"}, +] +"codespell 2.2.5" = [ + {url = "https://files.pythonhosted.org/packages/1d/bc/4bd1cdb7cf940ab8e8e619d3ad24c88b0257b030c6b0dd64cba3fdfa7bb8/codespell-2.2.5-py3-none-any.whl", hash = "sha256:efa037f54b73c84f7bd14ce8e853d5f822cdd6386ef0ff32e957a3919435b9ec"}, + {url = "https://files.pythonhosted.org/packages/81/30/e1b32067c551d745df2bdc9f1d510422d8a5819ca3b610b4433654cf578c/codespell-2.2.5.tar.gz", hash = "sha256:6d9faddf6eedb692bf80c9a94ec13ab4f5fb585aabae5f3750727148d7b5be56"}, +] +"colorama 0.4.6" = [ + {url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] +"coverage 7.3.0" = [ + {url = "https://files.pythonhosted.org/packages/01/40/a0f76d77a9a64947fc3dac90b0f62fbd7f4d02e62d10a7126f6785eb2cbe/coverage-7.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:211a4576e984f96d9fce61766ffaed0115d5dab1419e4f63d6992b480c2bd60b"}, + {url = "https://files.pythonhosted.org/packages/05/1d/45d448cfa9cdf7aea9ec49711a143c82afc793e9542f9ba9e3f5b83c4d4d/coverage-7.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9737bc49a9255d78da085fa04f628a310c2332b187cd49b958b0e494c125071"}, + {url = "https://files.pythonhosted.org/packages/09/7e/2f686c461ca6f28d32b248ec369c387798ec7e28a4525b2f79988c3f8164/coverage-7.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:90b6e2f0f66750c5a1178ffa9370dec6c508a8ca5265c42fbad3ccac210a7977"}, + {url = "https://files.pythonhosted.org/packages/0c/e5/724283de5799ce58e5efd5f1989919f115d9db273baa98befd99827c80cf/coverage-7.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:477c9430ad5d1b80b07f3c12f7120eef40bfbf849e9e7859e53b9c93b922d2af"}, + {url = "https://files.pythonhosted.org/packages/0d/5c/58e39b974c30b5d7d9b326162ddf303feac270cbc58166f0fbabee3bd124/coverage-7.3.0-cp38-cp38-win32.whl", hash = "sha256:bac329371d4c0d456e8d5f38a9b0816b446581b5f278474e416ea0c68c47dcd9"}, + {url = "https://files.pythonhosted.org/packages/14/07/bec5e18972f8c0d950dac5e6a36e8011583c2c0a4ab853063476cae3ff59/coverage-7.3.0-cp39-cp39-win32.whl", hash = "sha256:2d22172f938455c156e9af2612650f26cceea47dc86ca048fa4e0b2d21646ad3"}, + {url = "https://files.pythonhosted.org/packages/15/83/010ea70978c6deabc64542572dcddd43f27dfec2807fd853621702783fa2/coverage-7.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8f39c49faf5344af36042b293ce05c0d9004270d811c7080610b3e713251c9b0"}, + {url = "https://files.pythonhosted.org/packages/17/11/48d4804db0f3b0277a857b57ade93f03cb9f2afbce0e07c208a9f9b01805/coverage-7.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f297e0c1ae55300ff688568b04ff26b01c13dfbf4c9d2b7d0cb688ac60df479"}, + {url = "https://files.pythonhosted.org/packages/2a/b2/f2b519d33ececf73cf3d616fc7d051a73aa9609859fde376e902d79b69ce/coverage-7.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4c8e31cf29b60859876474034a83f59a14381af50cbe8a9dbaadbf70adc4b214"}, + {url = "https://files.pythonhosted.org/packages/2f/60/6fb960383f9159f67ba08924de6f8ac75aac6107c67dc9c6a533e0fccd3e/coverage-7.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b3eb0c93e2ea6445b2173da48cb548364f8f65bf68f3d090404080d338e3a689"}, + {url = "https://files.pythonhosted.org/packages/32/5a/d8e474e01fde6511bf8354df005248aeb2e3a71dacfe1624fbc2916a15f4/coverage-7.3.0-cp311-cp311-win32.whl", hash = "sha256:ccc51713b5581e12f93ccb9c5e39e8b5d4b16776d584c0f5e9e4e63381356482"}, + {url = "https://files.pythonhosted.org/packages/3a/58/9c4bb389ccc0ba9f9337d7e2f313a96dacbd2647e774cdc43de4325186d4/coverage-7.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:db76a1bcb51f02b2007adacbed4c88b6dee75342c37b05d1822815eed19edee5"}, + {url = "https://files.pythonhosted.org/packages/3b/5c/f4e217d026d0e1faef27dc0b1c7a89798bf5d4b8b013f5b7cceda85efc83/coverage-7.3.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37d5576d35fcb765fca05654f66aa71e2808d4237d026e64ac8b397ffa66a56a"}, + {url = "https://files.pythonhosted.org/packages/44/39/809e546b31d871e9636315d0097891ae3177e0f6da2021c489f64dbe00b7/coverage-7.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3c9834d5e3df9d2aba0275c9f67989c590e05732439b3318fa37a725dff51e74"}, + {url = "https://files.pythonhosted.org/packages/49/e0/2ab75b127e7948520562ad9047bcc3d1a186409f906083d5435799cc3332/coverage-7.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b859128a093f135b556b4765658d5d2e758e1fae3e7cc2f8c10f26fe7005e543"}, + {url = "https://files.pythonhosted.org/packages/4a/6b/4873ec302c4b7dc493620a6f82b5421262c5c4d1485544e93cf79ad7b989/coverage-7.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:34f9f0763d5fa3035a315b69b428fe9c34d4fc2f615262d6be3d3bf3882fb985"}, + {url = "https://files.pythonhosted.org/packages/4d/bd/1c3e5ccc7372fa7b65b294017444ef7b3040016349a3762c561ad271375a/coverage-7.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:97c44f4ee13bce914272589b6b41165bbb650e48fdb7bd5493a38bde8de730a1"}, + {url = "https://files.pythonhosted.org/packages/4e/87/c0163d39ac70cab62ebcaee164c988215cd312919a78940c2251a2fcfabb/coverage-7.3.0.tar.gz", hash = "sha256:49dbb19cdcafc130f597d9e04a29d0a032ceedf729e41b181f51cd170e6ee865"}, + {url = "https://files.pythonhosted.org/packages/55/63/f2dcc8f7f1587ae54bf8cc1c3b08e07e442633a953537dfaf658a0cbac2c/coverage-7.3.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fac440c43e9b479d1241fe9d768645e7ccec3fb65dc3a5f6e90675e75c3f3e3a"}, + {url = "https://files.pythonhosted.org/packages/56/61/0bc551ef5e4cd459c34e769969b080d667ea9b2b3265819d4ae1f8d07702/coverage-7.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b543302a3707245d454fc49b8ecd2c2d5982b50eb63f3535244fd79a4be0c99d"}, + {url = "https://files.pythonhosted.org/packages/5b/5b/4e7ec6cc17a0cb4afc1aa99e6877d5e2c6377cdfeac67dba39643e1d4809/coverage-7.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:85b7335c22455ec12444cec0d600533a238d6439d8d709d545158c1208483873"}, + {url = "https://files.pythonhosted.org/packages/5b/93/89a4968a39bc22c1561fb7202e8da5401c3b8df590aaf294399b64491d51/coverage-7.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:80501d1b2270d7e8daf1b64b895745c3e234289e00d5f0e30923e706f110334e"}, + {url = "https://files.pythonhosted.org/packages/62/b9/de6fc3a608b4c0438b96e120fe83304d39b6be640b14363004843602118d/coverage-7.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13c6cbbd5f31211d8fdb477f0f7b03438591bdd077054076eec362cf2207b4a7"}, + {url = "https://files.pythonhosted.org/packages/6c/d5/1bf0476b77b1466970a0d7a9982806efa3e5ab5c63f94db623c7458b97b7/coverage-7.3.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:5492a6ce3bdb15c6ad66cb68a0244854d9917478877a25671d70378bdc8562d0"}, + {url = "https://files.pythonhosted.org/packages/70/d5/95c3ff3a24d820c77cee9d09c27f4be0004e56538a1239af08a48f4e6dc1/coverage-7.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:07ea61bcb179f8f05ffd804d2732b09d23a1238642bf7e51dad62082b5019b34"}, + {url = "https://files.pythonhosted.org/packages/7a/6b/f16c757f34adaf76413b061ff412d599958a299dba5dfb9371e5567b77d9/coverage-7.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad0f87826c4ebd3ef484502e79b39614e9c03a5d1510cfb623f4a4a051edc6fd"}, + {url = "https://files.pythonhosted.org/packages/81/67/3ca6338b87d51371b7a8ffcb1ea8bd2161db26c4fc8cc1dbec37e5197d00/coverage-7.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49ab200acf891e3dde19e5aa4b0f35d12d8b4bd805dc0be8792270c71bd56c54"}, + {url = "https://files.pythonhosted.org/packages/82/a6/194198e62702d82ee581a035fcc5032a7bebc0264eb5ebffb466c6b5b4ea/coverage-7.3.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2ac9a1de294773b9fa77447ab7e529cf4fe3910f6a0832816e5f3d538cfea9a"}, + {url = "https://files.pythonhosted.org/packages/8b/ee/01ae9c6510b28fa6a904d9b00e60a6c54e51b276c0c343e72846a01e0e86/coverage-7.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4eddd3153d02204f22aef0825409091a91bf2a20bce06fe0f638f5c19a85de54"}, + {url = "https://files.pythonhosted.org/packages/8c/1f/a8132477bd5ca4f7e372c7d01bf8e844db6c0805f18d3d0e0b913e6cc22e/coverage-7.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:96d7d761aea65b291a98c84e1250cd57b5b51726821a6f2f8df65db89363be51"}, + {url = "https://files.pythonhosted.org/packages/8c/d9/e84edcf16e44e98b4e568d1cb1bfbeab43c91795af490a89c2ebcb370a7c/coverage-7.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97af9554a799bd7c58c0179cc8dbf14aa7ab50e1fd5fa73f90b9b7215874ba28"}, + {url = "https://files.pythonhosted.org/packages/91/26/6bfe51c92ac314e3c75c7d1e61c357bd79d52982ef3c4e0006f21d301efd/coverage-7.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3558e5b574d62f9c46b76120a5c7c16c4612dc2644c3d48a9f4064a705eaee95"}, + {url = "https://files.pythonhosted.org/packages/9b/01/49a4f47d87acc3be6cd0013c33b7ef6e1acc13f67ac9ff2fd1f7d73b4b12/coverage-7.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:59777652e245bb1e300e620ce2bef0d341945842e4eb888c23a7f1d9e143c446"}, + {url = "https://files.pythonhosted.org/packages/9b/31/2aafcfb1b132780dc66205935b260424e2665cea76d7597a4cbde1a3c4a9/coverage-7.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:438856d3f8f1e27f8e79b5410ae56650732a0dcfa94e756df88c7e2d24851fcd"}, + {url = "https://files.pythonhosted.org/packages/9e/3b/5bdcbd8c64abf4eb1d61addf11754ad5883f3bda1d612cc843cbb3958902/coverage-7.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c02cfa6c36144ab334d556989406837336c1d05215a9bdf44c0bc1d1ac1cb637"}, + {url = "https://files.pythonhosted.org/packages/a5/40/2e6791d3bca7734d6c2f42527aa9656630215a8324b459ae21bb98905251/coverage-7.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce2ee86ca75f9f96072295c5ebb4ef2a43cecf2870b0ca5e7a1cbdd929cf67e1"}, + {url = "https://files.pythonhosted.org/packages/a6/c5/c94da7b5ee14a0e7b046b2d59b50fe37d50ae78046e3459639961d3dccf5/coverage-7.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:74c160285f2dfe0acf0f72d425f3e970b21b6de04157fc65adc9fd07ee44177f"}, + {url = "https://files.pythonhosted.org/packages/a9/43/29bb5ceabd87bdff07ac29333a68828f210e7c2e928c85464e9264f7a8df/coverage-7.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5247bab12f84a1d608213b96b8af0cbb30d090d705b6663ad794c2f2a5e5b9fe"}, + {url = "https://files.pythonhosted.org/packages/b0/39/efd8ef79db5bf86a0bc7294cfdc67519d6f1d39e4732da47884b41134f30/coverage-7.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac0dec90e7de0087d3d95fa0533e1d2d722dcc008bc7b60e1143402a04c117c1"}, + {url = "https://files.pythonhosted.org/packages/b1/cb/48d62b864e408bea2608b4ce19ba1feba0ffbf5a03640cf024cb3122e895/coverage-7.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:887665f00ea4e488501ba755a0e3c2cfd6278e846ada3185f42d391ef95e7e70"}, + {url = "https://files.pythonhosted.org/packages/b4/bd/62e477c0d97a2055fa0889add92c6abd88e991961495779d9d5604bc586f/coverage-7.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:60f64e2007c9144375dd0f480a54d6070f00bb1a28f65c408370544091c9bc9e"}, + {url = "https://files.pythonhosted.org/packages/be/ed/cdeeb2a3c7803b98fb452f624bed905929508030dbdae8ea69930d555cda/coverage-7.3.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a67e6bbe756ed458646e1ef2b0778591ed4d1fcd4b146fc3ba2feb1a7afd4254"}, + {url = "https://files.pythonhosted.org/packages/c5/ad/1559ab85952a47531004f9a32bcac51f9755e9541fb03eae42a9358e00dd/coverage-7.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7a9baf8e230f9621f8e1d00c580394a0aa328fdac0df2b3f8384387c44083c0f"}, + {url = "https://files.pythonhosted.org/packages/cf/36/5ef7c06901b0ec540d044203931a12d01ba7b845cd913279d9aab1e907ae/coverage-7.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61260ec93f99f2c2d93d264b564ba912bec502f679793c56f678ba5251f0393"}, + {url = "https://files.pythonhosted.org/packages/d1/6b/b7f5e6e7ae64f0b8795dfb499ba73a5bae66131b518c1e5c448fb838d3c9/coverage-7.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:36ce5d43a072a036f287029a55b5c6a0e9bd73db58961a273b6dc11a2c6eb9c2"}, + {url = "https://files.pythonhosted.org/packages/d3/5e/0278a108b3fed55a4c9d6f21e1fc0ed3c081e18da2a9c716cffbb75c2d20/coverage-7.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fc0ed8d310afe013db1eedd37176d0839dc66c96bcfcce8f6607a73ffea2d6ba"}, + {url = "https://files.pythonhosted.org/packages/d6/83/a61789658d2441b848dec999b58ce453309f88dc1c62a723a04b8c8141c5/coverage-7.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7df91fb24c2edaabec4e0eee512ff3bc6ec20eb8dccac2e77001c1fe516c0c84"}, + {url = "https://files.pythonhosted.org/packages/dd/53/2de98835e2976d042fd30967e6b00d57e688cfcc17ad10f11dc2c307ec9c/coverage-7.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d000a739f9feed900381605a12a61f7aaced6beae832719ae0d15058a1e81c1b"}, + {url = "https://files.pythonhosted.org/packages/e3/b9/6244d38d1574bd13995025802dbc5577acd5aab143e53ddecc087d485a30/coverage-7.3.0-cp312-cp312-win32.whl", hash = "sha256:56afbf41fa4a7b27f6635bc4289050ac3ab7951b8a821bca46f5b024500e6321"}, + {url = "https://files.pythonhosted.org/packages/e6/b0/e15a3acf0dce6215afcd2186f53fd534d2b456208e078409431b9e70445a/coverage-7.3.0-cp310-cp310-win32.whl", hash = "sha256:63c5b8ecbc3b3d5eb3a9d873dec60afc0cd5ff9d9f1c75981d8c31cfe4df8527"}, + {url = "https://files.pythonhosted.org/packages/f1/87/8d833ab0815080171abe3845929cf82907b239b9e566435886d8d1113d72/coverage-7.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1084393c6bda8875c05e04fce5cfe1301a425f758eb012f010eab586f1f3905e"}, + {url = "https://files.pythonhosted.org/packages/f7/02/f24041262825e15425fc77ecc63555cf3741d3eec3ed06acdd4bdd636a9b/coverage-7.3.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68d8a0426b49c053013e631c0cdc09b952d857efa8f68121746b339912d27a12"}, +] +"cryptography 41.0.3" = [ + {url = "https://files.pythonhosted.org/packages/00/d7/51516ad1da024d331ed2f4f0f8836ec8373e4a6b3e3ac190753f1cd6fffe/cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:23c2d778cf829f7d0ae180600b17e9fceea3c2ef8b31a99e3c694cbbf3a24b84"}, + {url = "https://files.pythonhosted.org/packages/0e/c2/6b4463782ad828f89f45fd073adfaaca67eb71249488deeda00ae475f002/cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:67e120e9a577c64fe1f611e53b30b3e69744e5910ff3b6e97e935aeb96005858"}, + {url = "https://files.pythonhosted.org/packages/10/47/c6bc7aa374e74af9694eae95d4fecea2777ef4c309f5c4b404c7262a87d1/cryptography-41.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce785cf81a7bdade534297ef9e490ddff800d956625020ab2ec2780a556c313e"}, + {url = "https://files.pythonhosted.org/packages/21/74/a7ebb5bcf733b1626e4778941e505792d7f655e799ff3bdbd9a176516ee2/cryptography-41.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84537453d57f55a50a5b6835622ee405816999a7113267739a1b4581f83535bd"}, + {url = "https://files.pythonhosted.org/packages/30/56/5f4eee57ccd577c261b516bfcbe17492838e2bc4e2e92ea93bbb57666fbd/cryptography-41.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:a983e441a00a9d57a4d7c91b3116a37ae602907a7618b882c8013b5762e80574"}, + {url = "https://files.pythonhosted.org/packages/46/74/f9eba8c947f57991b5dd5e45797fdc68cc70e444c32e6b952b512d42aba5/cryptography-41.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:42cb413e01a5d36da9929baa9d70ca90d90b969269e5a12d39c1e0d475010116"}, + {url = "https://files.pythonhosted.org/packages/5c/83/50d61ceaf324d73dd2e41c38c7a9d0e522be4a31fca2a0fa70f39b2e4c50/cryptography-41.0.3-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:57a51b89f954f216a81c9d057bf1a24e2f36e764a1ca9a501a6964eb4a6800dd"}, + {url = "https://files.pythonhosted.org/packages/6c/02/2f4f33c5284ddee77efe89248a059dba27bead01a812a76729d51b0bcb3d/cryptography-41.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a74fbcdb2a0d46fe00504f571a2a540532f4c188e6ccf26f1f178480117b33c4"}, + {url = "https://files.pythonhosted.org/packages/79/18/5495f896421da0f5ae58f6cfaf6866269aa9b240206175fcefe1467a0d6b/cryptography-41.0.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:41d7aa7cdfded09b3d73a47f429c298e80796c8e825ddfadc84c8a7f12df212d"}, + {url = "https://files.pythonhosted.org/packages/7d/43/587996ab411ca9cc7b75927856783f1791390d57ab7dc5f2c24df61e3f9a/cryptography-41.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3fb248989b6363906827284cd20cca63bb1a757e0a2864d4c1682a985e3dca47"}, + {url = "https://files.pythonhosted.org/packages/8e/5d/2bf54672898375d081cb24b30baeb7793568ae5d958ef781349e9635d1c8/cryptography-41.0.3.tar.gz", hash = "sha256:6d192741113ef5e30d89dcb5b956ef4e1578f304708701b8b73d38e3e1461f34"}, + {url = "https://files.pythonhosted.org/packages/91/68/5c33bb0115b3413a974dd4d23625b99ed22522582b513f82e93ce00f954c/cryptography-41.0.3-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:652627a055cb52a84f8c448185922241dd5217443ca194d5739b44612c5e6507"}, + {url = "https://files.pythonhosted.org/packages/9a/90/4c779507b50c9adf3f11f973f22d80a83097100cf9e1766b21ec4cd0bba2/cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ab8de0d091acbf778f74286f4989cf3d1528336af1b59f3e5d2ebca8b5fe49e1"}, + {url = "https://files.pythonhosted.org/packages/9e/ac/e26bd0f1c96444c3332fcc32ecbdcfccc0356b8f0cc4db9047ddccb4d7c1/cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c2f0d35703d61002a2bbdcf15548ebb701cfdd83cdc12471d2bae80878a4207"}, + {url = "https://files.pythonhosted.org/packages/a2/e6/2331e5bde68343b820a9e5d937b2e22a0f81ba68e87b74dbbdd98944da4e/cryptography-41.0.3-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:8f09daa483aedea50d249ef98ed500569841d6498aa9c9f4b0531b9964658922"}, + {url = "https://files.pythonhosted.org/packages/ac/1b/0768c89d513bdefecc1f5ebb12df87e810d8a043c35c37a8cc7f3bef28c6/cryptography-41.0.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5259cb659aa43005eb55a0e4ff2c825ca111a0da1814202c64d28a985d33b087"}, + {url = "https://files.pythonhosted.org/packages/b7/d9/b3500bc80cc1ce775c987689c1bd2d9f75513df7ab78bdec0c6bad368ae5/cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d0d651aa754ef58d75cec6edfbd21259d93810b73f6ec246436a21b7841908de"}, + {url = "https://files.pythonhosted.org/packages/cc/65/65e6719b0038e2fece9311d39372f1f4293c32e8951edff78db857d62fc3/cryptography-41.0.3-cp37-abi3-win32.whl", hash = "sha256:0d09fb5356f975974dbcb595ad2d178305e5050656affb7890a1583f5e02a306"}, + {url = "https://files.pythonhosted.org/packages/d2/36/6fa85e93c92888e6e0afa233adbf22a0747ed3448032c5a92326dbb6faec/cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7efe8041897fe7a50863e51b77789b657a133c75c3b094e51b5e4b5cec7bf906"}, + {url = "https://files.pythonhosted.org/packages/ef/a4/5131f125a7c413b89c01cff9712c6405a4ac46909deba67d74209a45d973/cryptography-41.0.3-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6af1c6387c531cd364b72c28daa29232162010d952ceb7e5ca8e2827526aceae"}, + {url = "https://files.pythonhosted.org/packages/f6/09/b20b8c54f53fdd10c6971ce2eab32aecbabc2a7ab7621839653460f988fc/cryptography-41.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:95dd7f261bb76948b52a5330ba5202b91a26fbac13ad0e9fc8a3ac04752058c7"}, + {url = "https://files.pythonhosted.org/packages/f6/c3/3eff8181cd23aa5b33ead7c5086fbc9dee3f794fe782274ef1c61b16d613/cryptography-41.0.3-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:aeb57c421b34af8f9fe830e1955bf493a86a7996cc1338fe41b30047d16e962c"}, + {url = "https://files.pythonhosted.org/packages/ff/62/4b7f7d0e8c69ee9dc79238362af05df77ee7020123d922847665937e42d2/cryptography-41.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fd871184321100fb400d759ad0cddddf284c4b696568204d281c902fc7b0d81"}, +] +"deprecated 1.2.14" = [ + {url = "https://files.pythonhosted.org/packages/20/8d/778b7d51b981a96554f29136cd59ca7880bf58094338085bcf2a979a0e6a/Deprecated-1.2.14-py2.py3-none-any.whl", hash = "sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c"}, + {url = "https://files.pythonhosted.org/packages/92/14/1e41f504a246fc224d2ac264c227975427a85caf37c3979979edb9b1b232/Deprecated-1.2.14.tar.gz", hash = "sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3"}, +] +"dill 0.3.7" = [ + {url = "https://files.pythonhosted.org/packages/c4/31/54dd222e02311c2dbc9e680d37cbd50f4494ce1ee9b04c69980e4ec26f38/dill-0.3.7.tar.gz", hash = "sha256:cc1c8b182eb3013e24bd475ff2e9295af86c1a38eb1aff128dac8962a9ce3c03"}, + {url = "https://files.pythonhosted.org/packages/f5/3a/74a29b11cf2cdfcd6ba89c0cecd70b37cd1ba7b77978ce611eb7a146a832/dill-0.3.7-py3-none-any.whl", hash = "sha256:76b122c08ef4ce2eedcd4d1abd8e641114bfc6c2867f49f3c41facf65bf19f5e"}, +] +"distlib 0.3.7" = [ + {url = "https://files.pythonhosted.org/packages/29/34/63be59bdf57b3a8a8dcc252ef45c40f3c018777dc8843d45dd9b869868f0/distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, + {url = "https://files.pythonhosted.org/packages/43/a0/9ba967fdbd55293bacfc1507f58e316f740a3b231fc00e3d86dc39bc185a/distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, +] +"ecdsa 0.18.0" = [ + {url = "https://files.pythonhosted.org/packages/09/d4/4f05f5d16a4863b30ba96c23b23e942da8889abfa1cdbabf2a0df12a4532/ecdsa-0.18.0-py2.py3-none-any.whl", hash = "sha256:80600258e7ed2f16b9aa1d7c295bd70194109ad5a30fdee0eaeefef1d4c559dd"}, + {url = "https://files.pythonhosted.org/packages/ff/7b/ba6547a76c468a0d22de93e89ae60d9561ec911f59532907e72b0d8bc0f1/ecdsa-0.18.0.tar.gz", hash = "sha256:190348041559e21b22a1d65cee485282ca11a6f81d503fddb84d5017e9ed1e49"}, +] +"exceptiongroup 1.1.3" = [ + {url = "https://files.pythonhosted.org/packages/ad/83/b71e58666f156a39fb29417e4c8ca4bc7400c0dd4ed9e8842ab54dc8c344/exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, + {url = "https://files.pythonhosted.org/packages/c2/e1/5561ad26f99b7779c28356f73f69a8b468ef491d0f6adf20d7ed0ac98ec1/exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, +] +"execnet 2.0.2" = [ + {url = "https://files.pythonhosted.org/packages/e4/c8/d382dc7a1e68a165f4a4ab612a08b20d8534a7d20cc590630b734ca0c54b/execnet-2.0.2.tar.gz", hash = "sha256:cc59bc4423742fd71ad227122eb0dd44db51efb3dc4095b45ac9a08c770096af"}, + {url = "https://files.pythonhosted.org/packages/e8/9c/a079946da30fac4924d92dbc617e5367d454954494cf1e71567bcc4e00ee/execnet-2.0.2-py3-none-any.whl", hash = "sha256:88256416ae766bc9e8895c76a87928c0012183da3cc4fc18016e6f050e025f41"}, +] +"fastapi 0.79.1" = [ + {url = "https://files.pythonhosted.org/packages/29/95/31db8826daec0bfc9a4bb6ceac3ceeb7e8e6c6b81b6d204aaec788445c8c/fastapi-0.79.1-py3-none-any.whl", hash = "sha256:3c584179c64e265749e88221c860520fc512ea37e253282dab378cc503dfd7fd"}, + {url = "https://files.pythonhosted.org/packages/48/eb/feec98de25762193702e909535fbf4a1b3cb63617e6ee2e72c95ae4789ba/fastapi-0.79.1.tar.gz", hash = "sha256:006862dec0f0f5683ac21fb0864af2ff12a931e7ba18920f28cc8eceed51896b"}, +] +"filelock 3.12.2" = [ + {url = "https://files.pythonhosted.org/packages/00/0b/c506e9e44e4c4b6c89fcecda23dc115bf8e7ff7eb127e0cb9c114cbc9a15/filelock-3.12.2.tar.gz", hash = "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81"}, + {url = "https://files.pythonhosted.org/packages/00/45/ec3407adf6f6b5bf867a4462b2b0af27597a26bd3cd6e2534cb6ab029938/filelock-3.12.2-py3-none-any.whl", hash = "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec"}, +] +"freezegun 1.2.2" = [ + {url = "https://files.pythonhosted.org/packages/1d/97/002ac49ec52858538b4aa6f6831f83c2af562c17340bdf6043be695f39ac/freezegun-1.2.2.tar.gz", hash = "sha256:cd22d1ba06941384410cd967d8a99d5ae2442f57dfafeff2fda5de8dc5c05446"}, + {url = "https://files.pythonhosted.org/packages/50/cd/ba1c8319c002727ccfa03049127218d1767232a77219924d03ba170e0601/freezegun-1.2.2-py3-none-any.whl", hash = "sha256:ea1b963b993cb9ea195adbd893a48d573fda951b0da64f60883d7e988b606c9f"}, +] +"graphql-core 3.2.3" = [ + {url = "https://files.pythonhosted.org/packages/ee/a6/94df9045ca1bac404c7b394094cd06713f63f49c7a4d54d99b773ae81737/graphql-core-3.2.3.tar.gz", hash = "sha256:06d2aad0ac723e35b1cb47885d3e5c45e956a53bc1b209a9fc5369007fe46676"}, + {url = "https://files.pythonhosted.org/packages/f8/39/e5143e7ec70939d2076c1165ae9d4a3815597019c4d797b7f959cf778600/graphql_core-3.2.3-py3-none-any.whl", hash = "sha256:5766780452bd5ec8ba133f8bf287dc92713e3868ddd83aee4faab9fc3e303dc3"}, +] +"greenlet 2.0.2" = [ + {url = "https://files.pythonhosted.org/packages/07/ef/6bfa2ea34f76dea02833d66d28ae7cf4729ddab74ee93ee069c7f1d47c4f/greenlet-2.0.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:d5508f0b173e6aa47273bdc0a0b5ba055b59662ba7c7ee5119528f466585526b"}, + {url = "https://files.pythonhosted.org/packages/08/b1/0615df6393464d6819040124eb7bdff6b682f206a464b4537964819dcab4/greenlet-2.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dd11f291565a81d71dab10b7033395b7a3a5456e637cf997a6f33ebdf06f8db"}, + {url = "https://files.pythonhosted.org/packages/09/57/5fdd37939e0989a756a32d0a838409b68d1c5d348115e9c697f42ee4f87d/greenlet-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df"}, + {url = "https://files.pythonhosted.org/packages/09/93/d7ed73f82b6f1045dd5d98f063fa16da5273d0812c42f38229d28882762b/greenlet-2.0.2-cp39-cp39-win32.whl", hash = "sha256:ea9872c80c132f4663822dd2a08d404073a5a9b5ba6155bea72fb2a79d1093b5"}, + {url = "https://files.pythonhosted.org/packages/0a/46/96b37dcfe9c9d39b2d2f060a5775139ce8a440315a1ca2667a6b83a2860e/greenlet-2.0.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df"}, + {url = "https://files.pythonhosted.org/packages/0a/54/cbc1096b883b2d1c0c1454837f089971de814ba5ce42be04cf0716a06000/greenlet-2.0.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0f72c9ddb8cd28532185f54cc1453f2c16fb417a08b53a855c4e6a418edd099"}, + {url = "https://files.pythonhosted.org/packages/0d/f6/2d406a22767029e785154071bef79b296f91b92d1c199ec3c2202386bf04/greenlet-2.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b"}, + {url = "https://files.pythonhosted.org/packages/17/f9/7f5d755380d329e44307c2f6e52096740fdebb92e7e22516811aeae0aec0/greenlet-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91"}, + {url = "https://files.pythonhosted.org/packages/1d/a0/697653ea5e35acaf28e2a1246645ac512beb9b49a86b310fd0151b075342/greenlet-2.0.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6"}, + {url = "https://files.pythonhosted.org/packages/1e/1e/632e55a04d732c8184201238d911207682b119c35cecbb9a573a6c566731/greenlet-2.0.2.tar.gz", hash = "sha256:e7c8dc13af7db097bed64a051d2dd49e9f0af495c26995c00a9ee842690d34c0"}, + {url = "https://files.pythonhosted.org/packages/1f/42/95800f165d20fb8269fe6a3ac494649718ede074b1d8a78f58ee2ebda27a/greenlet-2.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:18e98fb3de7dba1c0a852731c3070cf022d14f0d68b4c87a19cc1016f3bb8b33"}, + {url = "https://files.pythonhosted.org/packages/20/28/c93ffaa75f3c907cd010bf44c5c18c7f8f4bb2409146bd67d538163e33b8/greenlet-2.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1a819eef4b0e0b96bb0d98d797bef17dc1b4a10e8d7446be32d1da33e095dbb8"}, + {url = "https://files.pythonhosted.org/packages/29/c4/fe82cb9ff1bffc52a3832e35fa49cce63e5d366808179153ee879ce47cc9/greenlet-2.0.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9d14b83fab60d5e8abe587d51c75b252bcc21683f24699ada8fb275d7712f5a9"}, + {url = "https://files.pythonhosted.org/packages/37/b9/3ebd606768bee3ef2198fe6d5e7c6c3af42ad3e06b56c1d0a89c56faba2a/greenlet-2.0.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b"}, + {url = "https://files.pythonhosted.org/packages/3a/69/a6d3d7abd0f36438ff5fab52572fd107966939d59ef9b8309263ab89f607/greenlet-2.0.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:910841381caba4f744a44bf81bfd573c94e10b3045ee00de0cbf436fe50673a6"}, + {url = "https://files.pythonhosted.org/packages/42/d0/285b81442d8552b1ae6a2ff38caeec94ab90507c9740da718189416e8e6e/greenlet-2.0.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:bdfea8c661e80d3c1c99ad7c3ff74e6e87184895bbaca6ee8cc61209f8b9b85d"}, + {url = "https://files.pythonhosted.org/packages/43/81/e0a656e3a417b172f834ba5a08dde02b55fd249416c1e933d62ffb6734d0/greenlet-2.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2780572ec463d44c1d3ae850239508dbeb9fed38e294c68d19a24d925d9223ca"}, + {url = "https://files.pythonhosted.org/packages/49/b8/3ee1723978245e6f0c087908689f424876803ec05555400681240ab2ab33/greenlet-2.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be4ed120b52ae4d974aa40215fcdfde9194d63541c7ded40ee12eb4dda57b76b"}, + {url = "https://files.pythonhosted.org/packages/4d/b2/32f737e1fcf67b23351b4860489029df562b41d7ffb568a3e1ae610f7a9b/greenlet-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7efde645ca1cc441d6dc4b48c0f7101e8d86b54c8530141b09fd31cef5149ec9"}, + {url = "https://files.pythonhosted.org/packages/50/3d/7e3d95b955722c514f982bdf6bbe92bb76218b0036dd9b093ae0c168d63a/greenlet-2.0.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30"}, + {url = "https://files.pythonhosted.org/packages/52/39/fa5212bc9ac588c62e52213d4fab30a348059842883410724f9d0408c0f4/greenlet-2.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343"}, + {url = "https://files.pythonhosted.org/packages/53/0f/637f6e18e1980ebd2eedd8a9918a7898a6fe44f6188f6f39c6d9181c9891/greenlet-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645"}, + {url = "https://files.pythonhosted.org/packages/54/ce/3a589ec27bd5de97707d2a193716bbe412ccbdb1479f0c3f990789c8fa8c/greenlet-2.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9c59a2120b55788e800d82dfa99b9e156ff8f2227f07c5e3012a45a399620b7"}, + {url = "https://files.pythonhosted.org/packages/57/a8/079c59b8f5406957224f4f4176e9827508d555beba6d8635787d694226d1/greenlet-2.0.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:18a7f18b82b52ee85322d7a7874e676f34ab319b9f8cce5de06067384aa8ff43"}, + {url = "https://files.pythonhosted.org/packages/5a/30/5eab5cbb99263c7d8305657587381c84da2a71fddb07dd5efbfaeecf7264/greenlet-2.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937e9020b514ceedb9c830c55d5c9872abc90f4b5862f89c0887033ae33c6f73"}, + {url = "https://files.pythonhosted.org/packages/6a/3d/77bd8dd7dd0b872eac87f1edf6fcd94d9d7666befb706ae3a08ed25fbea7/greenlet-2.0.2-cp36-cp36m-win32.whl", hash = "sha256:dbfcfc0218093a19c252ca8eb9aee3d29cfdcb586df21049b9d777fd32c14fd9"}, + {url = "https://files.pythonhosted.org/packages/6b/2f/1cb3f376df561c95cb61b199676f51251f991699e325a2aa5e12693d10b8/greenlet-2.0.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0"}, + {url = "https://files.pythonhosted.org/packages/6b/cd/84301cdf80360571f6aa77ac096f867ba98094fec2cb93e69c93d996b8f8/greenlet-2.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a"}, + {url = "https://files.pythonhosted.org/packages/6e/11/a1f1af20b6a1a8069bc75012569d030acb89fd7ef70f888b6af2f85accc6/greenlet-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d75209eed723105f9596807495d58d10b3470fa6732dd6756595e89925ce2470"}, + {url = "https://files.pythonhosted.org/packages/71/c5/c26840ce91bcbbfc42c1a246289d9d4c758663652669f24e37f84bcdae2a/greenlet-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5454276c07d27a740c5892f4907c86327b632127dd9abec42ee62e12427ff7e3"}, + {url = "https://files.pythonhosted.org/packages/7c/5f/ee39d27a08ae6b93f14faa953a6593dad888df75ae55ff479135e64ad4fe/greenlet-2.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acd2162a36d3de67ee896c43effcd5ee3de247eb00354db411feb025aa319857"}, + {url = "https://files.pythonhosted.org/packages/7c/f8/275f7fb1585d5e7dfbc18b4eb78282fbc85986f2eb8a185e7ebc60522cc2/greenlet-2.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:703f18f3fda276b9a916f0934d2fb6d989bf0b4fb5a64825260eb9bfd52d78f0"}, + {url = "https://files.pythonhosted.org/packages/7e/a6/0a34cde83fe520fa4e8192a1bc0fc7bf9f755215fefe3f42c9b97c45c620/greenlet-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:7cafd1208fdbe93b67c7086876f061f660cfddc44f404279c1585bbf3cdc64c5"}, + {url = "https://files.pythonhosted.org/packages/83/d1/cc273f8f5908940d6666a3db8637d2e24913a2e8e5034012b19ac291a2a0/greenlet-2.0.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:3c9b12575734155d0c09d6c3e10dbd81665d5c18e1a7c6597df72fd05990c8cf"}, + {url = "https://files.pythonhosted.org/packages/86/8d/3a18311306830f6db5f5676a1cb8082c8943bfa6c928b40006e5358170fc/greenlet-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2"}, + {url = "https://files.pythonhosted.org/packages/93/40/db2803f88326149ddcd1c00092e1e36ef55d31922812863753143a9aca01/greenlet-2.0.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd021c754b162c0fb55ad5d6b9d960db667faad0fa2ff25bb6e1301b0b6e6a75"}, + {url = "https://files.pythonhosted.org/packages/9d/ae/8ee23a9b63f854acc66ed0da7220130d87c861153cbc8ea07d11b61567f1/greenlet-2.0.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b9ec052b06a0524f0e35bd8790686a1da006bd911dd1ef7d50b77bfbad74e292"}, + {url = "https://files.pythonhosted.org/packages/a1/ea/66e69cf3034be99a1959b2bdd178f5176979e0e63107a37a194c90c49b40/greenlet-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40"}, + {url = "https://files.pythonhosted.org/packages/a3/6c/dde49c63ab2f12d2ce401620dbe1a80830109f5f310bdd2f96d2e259de37/greenlet-2.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:9f35ec95538f50292f6d8f2c9c9f8a3c6540bbfec21c9e5b4b751e0a7c20864f"}, + {url = "https://files.pythonhosted.org/packages/a8/7a/5542d863a91b3309585219bae7d97aa82fe0482499a840c100297262ec8f/greenlet-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c"}, + {url = "https://files.pythonhosted.org/packages/aa/21/6bbd8062fee551f747f5334b7ccd503693704ac4f3183fd8232e2af77bff/greenlet-2.0.2-cp35-cp35m-win32.whl", hash = "sha256:03a8f4f3430c3b3ff8d10a2a86028c660355ab637cee9333d63d66b56f09d52a"}, + {url = "https://files.pythonhosted.org/packages/ac/4a/3ceafef892b8428f77468506bc5a12d835fb9f150129d1a9704902cb4a2a/greenlet-2.0.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4b58adb399c4d61d912c4c331984d60eb66565175cdf4a34792cd9600f21b394"}, + {url = "https://files.pythonhosted.org/packages/b3/89/1d3b78577a6b2762cb254f6ce5faec9b7c7b23052d1cdb7237273ff37d10/greenlet-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:db1a39669102a1d8d12b57de2bb7e2ec9066a6f2b3da35ae511ff93b01b5d564"}, + {url = "https://files.pythonhosted.org/packages/c4/92/bbd9373fb022c21d1c41bc74b043d8d007825f80bb9534f0dd2f7ed62bca/greenlet-2.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a51c9751078733d88e013587b108f1b7a1fb106d402fb390740f002b6f6551a"}, + {url = "https://files.pythonhosted.org/packages/c5/ab/a69a875a45474cc5776b879258bfa685e99aae992ab310a0b8f773fe56a0/greenlet-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088"}, + {url = "https://files.pythonhosted.org/packages/c7/c9/2637e49b0ef3f17d7eaa52c5af5bfbda5f058e8ee97bd9418978b90e1169/greenlet-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526"}, + {url = "https://files.pythonhosted.org/packages/ca/1a/90f2ae7e3df48cbd42af5df47cf9ee37a6c6a78b1941acbc7eac029f5a44/greenlet-2.0.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae"}, + {url = "https://files.pythonhosted.org/packages/cd/e8/1ebc8f07d795c3677247e37dae23463a655636a4be387b0d739fa8fd9b2f/greenlet-2.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb"}, + {url = "https://files.pythonhosted.org/packages/d2/28/5cf37650334935c6a51313c70c4ec00fb1fad801a551c36afcfc9c03e80b/greenlet-2.0.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:32e5b64b148966d9cccc2c8d35a671409e45f195864560829f395a54226408d3"}, + {url = "https://files.pythonhosted.org/packages/d6/c4/f91d771a6628155676765c419c70d6d0ede9b5f3c023102c47ee2f45eadf/greenlet-2.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:36abbf031e1c0f79dd5d596bfaf8e921c41df2bdf54ee1eed921ce1f52999a86"}, + {url = "https://files.pythonhosted.org/packages/da/45/2600faf65f318767d2c24b6fce6bb0ad3721e8cb3eb9d7743aefcca8a6a6/greenlet-2.0.2-cp38-cp38-win32.whl", hash = "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249"}, + {url = "https://files.pythonhosted.org/packages/e5/ad/91a8f63881c862bb396cefc33d7faa241bf200df7ba96a1961a99329ed15/greenlet-2.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0bf60faf0bc2468089bdc5edd10555bab6e85152191df713e2ab1fcc86382b5a"}, + {url = "https://files.pythonhosted.org/packages/e6/0e/591ea935b63aa3aed3836976779e5d1324aa4b2961f7355ff5d1f296066b/greenlet-2.0.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:f82d4d717d8ef19188687aa32b8363e96062911e63ba22a0cff7802a8e58e5f1"}, + {url = "https://files.pythonhosted.org/packages/e8/3a/ebc4fa1e813ae1fa718eb88417c31587e2efb743ed5f6ff0ae066502c349/greenlet-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca"}, + {url = "https://files.pythonhosted.org/packages/e9/29/2ae545c4c0218b042c2bb0760c0f65e114cca1ab5e552dc23b0f118e428a/greenlet-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94c817e84245513926588caf1152e3b559ff794d505555211ca041f032abbb6b"}, + {url = "https://files.pythonhosted.org/packages/f0/2e/20eab0fa6353a08b0de055dd54e2575a6869ee693d86387076430475832d/greenlet-2.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:eff4eb9b7eb3e4d0cae3d28c283dc16d9bed6b193c2e1ace3ed86ce48ea8df19"}, + {url = "https://files.pythonhosted.org/packages/f4/ad/287efe1d3c8224fa5f9457195a842fc0c4fa4956cb9655a1f4e89914a313/greenlet-2.0.2-cp27-cp27m-win32.whl", hash = "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74"}, + {url = "https://files.pythonhosted.org/packages/f6/04/74e97d545f9276dee994b959eab3f7d70d77588e5aaedc383d15b0057acd/greenlet-2.0.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8"}, + {url = "https://files.pythonhosted.org/packages/fa/9a/e0e99a4aa93b16dd58881acb55ac1e2fb011475f2e46cf87843970001882/greenlet-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3"}, + {url = "https://files.pythonhosted.org/packages/fc/80/0ed0da38bbb978f39128d7e53ee51c36bed2e4a7460eff92981a3d07f1d4/greenlet-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7"}, +] +"h11 0.14.0" = [ + {url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] +"httptools 0.6.0" = [ + {url = "https://files.pythonhosted.org/packages/0f/40/26a5bd40bf12c40345fe7eb123e0077162f8896ee079af153b3ffe909285/httptools-0.6.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:73e9d66a5a28b2d5d9fbd9e197a31edd02be310186db423b28e6052472dc8201"}, + {url = "https://files.pythonhosted.org/packages/1b/51/ad5ec00731c00a4679ac2d8aaaf439579974fec969d52ec0300a9ec821e2/httptools-0.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:72ec7c70bd9f95ef1083d14a755f321d181f046ca685b6358676737a5fecd26a"}, + {url = "https://files.pythonhosted.org/packages/24/25/f6d51f2f464411eb103e56caeec0d215efb4cddd95f87d708c6def7aa848/httptools-0.6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fb4a608c631f7dcbdf986f40af7a030521a10ba6bc3d36b28c1dc9e9035a3c0"}, + {url = "https://files.pythonhosted.org/packages/25/34/787a9035536f00641e41c9aaead1477d1538c18a8606aa61c9edca7f136c/httptools-0.6.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:26326e0a8fe56829f3af483200d914a7cd16d8d398d14e36888b56de30bec81a"}, + {url = "https://files.pythonhosted.org/packages/26/3d/7d236d77a8ff9306137df7aa3bc6a79c8c53f78f13fb8ad9626a9be1aec2/httptools-0.6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35a541579bed0270d1ac10245a3e71e5beeb1903b5fbbc8d8b4d4e728d48ff1d"}, + {url = "https://files.pythonhosted.org/packages/27/19/b13b3815aa50cffcbd00f9505d35e435781cdcecebd7cd4242b0f5b4f544/httptools-0.6.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3f96d2a351b5625a9fd9133c95744e8ca06f7a4f8f0b8231e4bbaae2c485046a"}, + {url = "https://files.pythonhosted.org/packages/27/c1/58ad85d57a528bff8edd8b9004deadaded11b1195c58b79a39f600c05377/httptools-0.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4e748fc0d5c4a629988ef50ac1aef99dfb5e8996583a73a717fc2cac4ab89932"}, + {url = "https://files.pythonhosted.org/packages/28/7c/5ddc99737fb141bd9077f45af23e9d3c83496b4c04bf463e4e72f57043cd/httptools-0.6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82c723ed5982f8ead00f8e7605c53e55ffe47c47465d878305ebe0082b6a1755"}, + {url = "https://files.pythonhosted.org/packages/2b/15/a48d8036bf6ed80201f41479df1813ad1e01b48284281edcbefd05c3a364/httptools-0.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:23b09537086a5a611fad5696fc8963d67c7e7f98cb329d38ee114d588b0b74cd"}, + {url = "https://files.pythonhosted.org/packages/30/7c/9821f018649fb3d175df0293adea3b9158edb75c1328853448f02d52323d/httptools-0.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b703d15dbe082cc23266bf5d9448e764c7cb3fcfe7cb358d79d3fd8248673ef9"}, + {url = "https://files.pythonhosted.org/packages/36/9c/8f237d959e047b1c6d00131c063ce7c5ca48a64fd9b845c94bcb56c9d98c/httptools-0.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72205730bf1be875003692ca54a4a7c35fac77b4746008966061d9d41a61b0f5"}, + {url = "https://files.pythonhosted.org/packages/45/6c/e5e256103be70d24319f43266be83d1d8c9bfce39c806602601b0635845e/httptools-0.6.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:cf8169e839a0d740f3d3c9c4fa630ac1a5aaf81641a34575ca6773ed7ce041a1"}, + {url = "https://files.pythonhosted.org/packages/4e/4d/21f6a90385a54a05dcda1ca61835bed2aad3dd9003d71ed34265a2a1284c/httptools-0.6.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:93f89975465133619aea8b1952bc6fa0e6bad22a447c6d982fc338fbb4c89649"}, + {url = "https://files.pythonhosted.org/packages/51/9d/638ce3ce7ef549f6bb6b2b89369b278e8fd4b47cbebc1bf028e1a68608ac/httptools-0.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d0b0571806a5168013b8c3d180d9f9d6997365a4212cb18ea20df18b938aa0b"}, + {url = "https://files.pythonhosted.org/packages/5e/62/d7620a822006cb61506ed730cf622c0678cb89fa03372820e9d328cd4a67/httptools-0.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:259920bbae18740a40236807915def554132ad70af5067e562f4660b62c59b90"}, + {url = "https://files.pythonhosted.org/packages/61/46/c06a1f2a35d961204c1fd38700220df71ddcf5d01ff0f4cae73e28b67480/httptools-0.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:274bf20eeb41b0956e34f6a81f84d26ed57c84dd9253f13dcb7174b27ccd8aaf"}, + {url = "https://files.pythonhosted.org/packages/62/c7/eca648bc8eb24caf5e1614e80d3a189187eec3ce50c6f4dda545398eb267/httptools-0.6.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6e22896b42b95b3237eccc42278cd72c0df6f23247d886b7ded3163452481e38"}, + {url = "https://files.pythonhosted.org/packages/73/0f/2a76cef72e35b0696bf61d2458eaff3b5c1ac728e9090b6a87045d65e1c9/httptools-0.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33eb1d4e609c835966e969a31b1dedf5ba16b38cab356c2ce4f3e33ffa94cad3"}, + {url = "https://files.pythonhosted.org/packages/80/e6/158e8d56f6b4d29295cfa244f1e79a65d0987bb3941d36863e7f6e06e3b6/httptools-0.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:38f3cafedd6aa20ae05f81f2e616ea6f92116c8a0f8dcb79dc798df3356836e2"}, + {url = "https://files.pythonhosted.org/packages/83/e0/5c87bf475dd666bed3e8a949cb2f098b296a74bc534cd6882098aeb0d41c/httptools-0.6.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b0a816bb425c116a160fbc6f34cece097fd22ece15059d68932af686520966bd"}, + {url = "https://files.pythonhosted.org/packages/85/df/63720eadbadca00b7f91bc724972ca3a946670598354bef1de779c7c62e2/httptools-0.6.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:721e503245d591527cddd0f6fd771d156c509e831caa7a57929b55ac91ee2b51"}, + {url = "https://files.pythonhosted.org/packages/8a/a6/4ee791339f70776a0d57bd066f3721a24491a8b4b4faf572036d163296d1/httptools-0.6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03bfd2ae8a2d532952ac54445a2fb2504c804135ed28b53fefaf03d3a93eb1fd"}, + {url = "https://files.pythonhosted.org/packages/8f/71/d535e9f6967958d21b8fe1baeb7efb6304b86e8fcff44d0bda8690e0aec9/httptools-0.6.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:818325afee467d483bfab1647a72054246d29f9053fd17cc4b86cda09cc60339"}, + {url = "https://files.pythonhosted.org/packages/93/eb/dfa4990960c1c3a75d2b5a75e44f358a94530a988e2380ebc29782166fe5/httptools-0.6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:47043a6e0ea753f006a9d0dd076a8f8c99bc0ecae86a0888448eb3076c43d717"}, + {url = "https://files.pythonhosted.org/packages/a0/35/3861af367612c20b74862d474c495c2818037287118ed0d3e598cf48439a/httptools-0.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5dcc14c090ab57b35908d4a4585ec5c0715439df07be2913405991dbb37e049d"}, + {url = "https://files.pythonhosted.org/packages/ae/73/eae64945f3bc9d25a57cb1bc35e8fdf2247a9fd027b1749855aeedcb0bcb/httptools-0.6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65d802e7b2538a9756df5acc062300c160907b02e15ed15ba035b02bce43e89c"}, + {url = "https://files.pythonhosted.org/packages/bf/51/1e2f9691821f715ac63b9c8f6592a36c9025d09bf0ecf19832290480ee8e/httptools-0.6.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:dea66d94e5a3f68c5e9d86e0894653b87d952e624845e0b0e3ad1c733c6cc75d"}, + {url = "https://files.pythonhosted.org/packages/c5/fa/aced15396316b401e74b587610b503ce2b0613b027188f1456bca164ce94/httptools-0.6.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:463c3bc5ef64b9cf091be9ac0e0556199503f6e80456b790a917774a616aff6e"}, + {url = "https://files.pythonhosted.org/packages/cf/09/b17fbf88d5c285e7cd8162539ba6f95c778dcd47e44240aa14afd0982bb8/httptools-0.6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdc6675ec6cb79d27e0575750ac6e2b47032742e24eed011b8db73f2da9ed40"}, + {url = "https://files.pythonhosted.org/packages/d5/63/f1594d00b4ef9c137edc0ff202d84e684b6989f9b8b4d1475098ca320c9d/httptools-0.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:0781fedc610293a2716bc7fa142d4c85e6776bc59d617a807ff91246a95dea35"}, + {url = "https://files.pythonhosted.org/packages/dc/ba/97266d7c207fb6572bb8ec4154849812b26169442e17aae0e58cc7ceb7fb/httptools-0.6.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f959e4770b3fc8ee4dbc3578fd910fab9003e093f20ac8c621452c4d62e517cb"}, + {url = "https://files.pythonhosted.org/packages/dd/d7/79d8374e5aebd87c630b349313771fc1b24bf377af7e429b12c17979690b/httptools-0.6.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e41ccac9e77cd045f3e4ee0fc62cbf3d54d7d4b375431eb855561f26ee7a9ec4"}, + {url = "https://files.pythonhosted.org/packages/e3/3f/e976f6cf3da5e9dbda0096a0e65c1b109036a562658e020971ec54d007fb/httptools-0.6.0.tar.gz", hash = "sha256:9fc6e409ad38cbd68b177cd5158fc4042c796b82ca88d99ec78f07bed6c6b796"}, + {url = "https://files.pythonhosted.org/packages/ef/3f/dc03c90b23107ff68c3b8a48008a6f0bc07628bee900f187356a57c0dbe6/httptools-0.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:22c01fcd53648162730a71c42842f73b50f989daae36534c818b3f5050b54589"}, + {url = "https://files.pythonhosted.org/packages/ef/89/00b9805ac1205572afa4f2042542365b8cecf53ffddd1df635fcdde6fa0d/httptools-0.6.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:82f228b88b0e8c6099a9c4757ce9fdbb8b45548074f8d0b1f0fc071e35655d1c"}, +] +"identify 2.5.26" = [ + {url = "https://files.pythonhosted.org/packages/63/53/6e728e41c2fc620f020cff3e9c8c36f24a1b9be5e983f1bc3778e8609a34/identify-2.5.26-py2.py3-none-any.whl", hash = "sha256:c22a8ead0d4ca11f1edd6c9418c3220669b3b7533ada0a0ffa6cc0ef85cf9b54"}, + {url = "https://files.pythonhosted.org/packages/93/1f/19d02def7b525871585516278838510b8ae618723e47500eff9fc67363ff/identify-2.5.26.tar.gz", hash = "sha256:7243800bce2f58404ed41b7c002e53d4d22bcf3ae1b7900c2d7aefd95394bf7f"}, +] +"idna 3.4" = [ + {url = "https://files.pythonhosted.org/packages/8b/e1/43beb3d38dba6cb420cefa297822eac205a277ab43e5ba5d5c46faf96438/idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, + {url = "https://files.pythonhosted.org/packages/fc/34/3030de6f1370931b9dbb4dad48f6ab1015ab1d32447850b9fc94e60097be/idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, +] +"importlib-metadata 6.8.0" = [ + {url = "https://files.pythonhosted.org/packages/33/44/ae06b446b8d8263d712a211e959212083a5eda2bf36d57ca7415e03f6f36/importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, + {url = "https://files.pythonhosted.org/packages/cc/37/db7ba97e676af155f5fcb1a35466f446eadc9104e25b83366e8088c9c926/importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, +] +"importlib-resources 6.0.1" = [ + {url = "https://files.pythonhosted.org/packages/25/d4/592f53ce2f8dde8be5720851bd0ab71cc2e76c55978e4163ef1ab7e389bb/importlib_resources-6.0.1-py3-none-any.whl", hash = "sha256:134832a506243891221b88b4ae1213327eea96ceb4e407a00d790bb0626f45cf"}, + {url = "https://files.pythonhosted.org/packages/fd/dc/0c5cfbd4df5d6e83de4e64324b370151ee88de25f3c71aea21115f4f77f8/importlib_resources-6.0.1.tar.gz", hash = "sha256:4359457e42708462b9626a04657c6208ad799ceb41e5c58c57ffa0e6a098a5d4"}, +] +"iniconfig 2.0.0" = [ + {url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, + {url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, +] +"isort 5.12.0" = [ + {url = "https://files.pythonhosted.org/packages/0a/63/4036ae70eea279c63e2304b91ee0ac182f467f24f86394ecfe726092340b/isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"}, + {url = "https://files.pythonhosted.org/packages/a9/c4/dc00e42c158fc4dda2afebe57d2e948805c06d5169007f1724f0683010a9/isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"}, +] +"kombu 5.3.1" = [ + {url = "https://files.pythonhosted.org/packages/63/58/b23b9c1ffb30d8b5cdfc7bdecb17bfd7ea20c619e86e515297b496177144/kombu-5.3.1-py3-none-any.whl", hash = "sha256:48ee589e8833126fd01ceaa08f8a2041334e9f5894e5763c8486a550454551e9"}, + {url = "https://files.pythonhosted.org/packages/c8/69/b703f8ec8d0406be22534dad885cac847fe092b793c4893034e3308feb9b/kombu-5.3.1.tar.gz", hash = "sha256:fbd7572d92c0bf71c112a6b45163153dea5a7b6a701ec16b568c27d0fd2370f2"}, +] +"lazy-object-proxy 1.9.0" = [ + {url = "https://files.pythonhosted.org/packages/00/74/46a68f51457639c0cd79e385e2f49c0fa7324470997ac096108669c1e182/lazy_object_proxy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:946d27deaff6cf8452ed0dba83ba38839a87f4f7a9732e8f9fd4107b21e6ff07"}, + {url = "https://files.pythonhosted.org/packages/11/04/fa820296cb937b378d801cdc81c19de06624cfed481c1b8a6b439255a188/lazy_object_proxy-1.9.0-cp37-cp37m-win32.whl", hash = "sha256:f12ad7126ae0c98d601a7ee504c1122bcef553d1d5e0c3bfa77b16b3968d2734"}, + {url = "https://files.pythonhosted.org/packages/11/fe/be1eb76d83f1b5242c492b410ce86c59db629c0b0f0f8e75018dfd955c30/lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f5fa4a61ce2438267163891961cfd5e32ec97a2c444e5b842d574251ade27d2"}, + {url = "https://files.pythonhosted.org/packages/16/f2/e74981dedeb1a858cd5db9bcec81c4107da374249bc6894613472e01996f/lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0117049dd1d5635bbff65444496c90e0baa48ea405125c088e93d9cf4525b11"}, + {url = "https://files.pythonhosted.org/packages/18/1b/04ac4490a62ae1916f88e629e74192ada97d74afc927453d005f003e5a8f/lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f83ac4d83ef0ab017683d715ed356e30dd48a93746309c8f3517e1287523ef4"}, + {url = "https://files.pythonhosted.org/packages/1d/5d/25b9007c65f45805e711b56beac50ba395214e9e556cc8ee57f0882f88a9/lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8c6cfb338b133fbdbc5cfaa10fe3c6aeea827db80c978dbd13bc9dd8526b7d4"}, + {url = "https://files.pythonhosted.org/packages/20/c0/8bab72a73607d186edad50d0168ca85bd2743cfc55560c9d721a94654b20/lazy-object-proxy-1.9.0.tar.gz", hash = "sha256:659fb5809fa4629b8a1ac5106f669cfc7bef26fbb389dda53b3e010d1ac4ebae"}, + {url = "https://files.pythonhosted.org/packages/27/a1/7cc10ca831679c5875c18ae6e0a468f7787ecd31fdd53598f91ea50df58d/lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:721532711daa7db0d8b779b0bb0318fa87af1c10d7fe5e52ef30f8eff254d0cd"}, + {url = "https://files.pythonhosted.org/packages/31/ad/e8605300f51061284cc57ca0f4ef582047c7f309bda1bb1c3c19b64af5c9/lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66a3de4a3ec06cd8af3f61b8e1ec67614fbb7c995d02fa224813cb7afefee701"}, + {url = "https://files.pythonhosted.org/packages/4c/a4/cdd6f41a675a89ab584c78019a24adc533829764bcc85b0e24ed2678020c/lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7322c3d6f1766d4ef1e51a465f47955f1e8123caee67dd641e67d539a534d006"}, + {url = "https://files.pythonhosted.org/packages/4d/7b/a959ff734bd3d8df7b761bfeaec6428549b77267072676a337b774f3b3ef/lazy_object_proxy-1.9.0-cp310-cp310-win32.whl", hash = "sha256:f0705c376533ed2a9e5e97aacdbfe04cecd71e0aa84c7c0595d02ef93b6e4455"}, + {url = "https://files.pythonhosted.org/packages/4e/cb/aca3f4d89d3efbed724fd9504a96dafbe2d903ea908355a335acb110a5cd/lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:660c94ea760b3ce47d1855a30984c78327500493d396eac4dfd8bd82041b22be"}, + {url = "https://files.pythonhosted.org/packages/51/28/5c6dfb51df2cbb6d771a3b0d009f1edeab01f5cb16303ce32764b01636c0/lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfb38f9ffb53b942f2b5954e0f610f1e721ccebe9cce9025a38c8ccf4a5183a4"}, + {url = "https://files.pythonhosted.org/packages/5b/a6/3c0a8b2ad6ce7af133ed54321b0ead5509303be3a80f98506af198e50cb7/lazy_object_proxy-1.9.0-cp38-cp38-win32.whl", hash = "sha256:0a891e4e41b54fd5b8313b96399f8b0e173bbbfc03c7631f01efbe29bb0bcf82"}, + {url = "https://files.pythonhosted.org/packages/5c/76/0b16dc53e9ee5b24c621d808f46cca11e5e86c602b6bcd6dc27f9504af5b/lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:09763491ce220c0299688940f8dc2c5d05fd1f45af1e42e636b2e8b2303e4382"}, + {url = "https://files.pythonhosted.org/packages/69/1f/51657d681711476287c9ff643428be0f9663addc1d341d4be1bad89290bd/lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e7c21c95cae3c05c14aafffe2865bbd5e377cfc1348c4f7751d9dc9a48ca4bda"}, + {url = "https://files.pythonhosted.org/packages/69/da/58391196d8a41fa8fa69b47e8a7893f279d369939e4994b3bc8648ff0433/lazy_object_proxy-1.9.0-cp39-cp39-win32.whl", hash = "sha256:9090d8e53235aa280fc9239a86ae3ea8ac58eff66a705fa6aa2ec4968b95c821"}, + {url = "https://files.pythonhosted.org/packages/70/e7/f3735f8e47cb29a207568c5b8d28d9f5673228789b66cb0c48d488a37f94/lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:212774e4dfa851e74d393a2370871e174d7ff0ebc980907723bb67d25c8a7c30"}, + {url = "https://files.pythonhosted.org/packages/82/ac/d079d3ad377ba72e29d16ac077f8626ad4d3f55369c93168d0b81153d9a2/lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:18b78ec83edbbeb69efdc0e9c1cb41a3b1b1ed11ddd8ded602464c3fc6020494"}, + {url = "https://files.pythonhosted.org/packages/86/93/e921f7a795e252df7248e0d220dc69a9443ad507fe258dea51a32e5435ca/lazy_object_proxy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b40387277b0ed2d0602b8293b94d7257e17d1479e257b4de114ea11a8cb7f2d7"}, + {url = "https://files.pythonhosted.org/packages/8d/6d/10420823a979366bf43ca5e69433c0c588865883566b96b6e3ed5b51c1f8/lazy_object_proxy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:f2457189d8257dd41ae9b434ba33298aec198e30adf2dcdaaa3a28b9994f6adb"}, + {url = "https://files.pythonhosted.org/packages/9d/d7/81d466f2e69784bd416797824a2b8794aaf0b864a2390062ea197f06f0fc/lazy_object_proxy-1.9.0-cp311-cp311-win32.whl", hash = "sha256:81fc4d08b062b535d95c9ea70dbe8a335c45c04029878e62d744bdced5141586"}, + {url = "https://files.pythonhosted.org/packages/a7/51/6626c133e966698d53d65bcd90e34ad4986c5f0968c2328b3e9de51dbcf1/lazy_object_proxy-1.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9e25ef10a39e8afe59a5c348a4dbf29b4868ab76269f81ce1674494e2565a6e"}, + {url = "https://files.pythonhosted.org/packages/a8/32/c1a67f76ec6923a8a8b1bc006b7cb3d195e386e03fe56e20fe38fce0321e/lazy_object_proxy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9e7551208b2aded9c1447453ee366f1c4070602b3d932ace044715d89666899b"}, + {url = "https://files.pythonhosted.org/packages/b0/78/78962cb6f6d31a952acbc54e7838a5a85d952144973bd6e7ad24323dd466/lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:189bbd5d41ae7a498397287c408617fe5c48633e7755287b21d741f7db2706a9"}, + {url = "https://files.pythonhosted.org/packages/b1/80/2d9583fa8e5ac47ef183d811d26d833477b7ed02b64c17dd2ede68a3f9cf/lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cbf9b082426036e19c6924a9ce90c740a9861e2bdc27a4834fd0a910742ac1e8"}, + {url = "https://files.pythonhosted.org/packages/c9/8f/c8aab72c72634de0c726a98a1e4c84a93ef20049ee0427c871214f6a58d5/lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f699ac1c768270c9e384e4cbd268d6e67aebcfae6cd623b4d7c3bfde5a35db59"}, + {url = "https://files.pythonhosted.org/packages/cd/b6/84efe6e878e94f20cf9564ac3ede5e98d37c692b07080aef50cc4341052e/lazy_object_proxy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0daa332786cf3bb49e10dc6a17a52f6a8f9601b4cf5c295a4f85854d61de63"}, + {url = "https://files.pythonhosted.org/packages/d0/f4/95999792ce5f9223bac10cb31b1724723ecd0ba13e081c5fb806d7f5b9c4/lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1aa3de4088c89a1b69f8ec0dcc169aa725b0ff017899ac568fe44ddc1396df46"}, + {url = "https://files.pythonhosted.org/packages/db/92/284ab10a6d0f82da36a20d9c1464c30bb318d1a6dd0ae476de9f890e7abd/lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8fa02eaab317b1e9e03f69aab1f91e120e7899b392c4fc19807a8278a07a97e8"}, + {url = "https://files.pythonhosted.org/packages/e7/86/ec93d495197f1006d7c9535e168fe763b3cc21928fb35c8f9ce08944b614/lazy_object_proxy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:ea806fd4c37bf7e7ad82537b0757999264d5f70c45468447bb2b91afdbe73a6e"}, + {url = "https://files.pythonhosted.org/packages/ed/9b/44c370c8bbba32fd0217b4f15ca99f750d669d653c7f1eefa051627710e8/lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cd077f3d04a58e83d04b20e334f678c2b0ff9879b9375ed107d5d07ff160171"}, + {url = "https://files.pythonhosted.org/packages/f5/4f/9ad496dc26a10ed9ab8f088732f08dc1f88488897d6c9ac5e3432a254c30/lazy_object_proxy-1.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:edd20c5a55acb67c7ed471fa2b5fb66cb17f61430b7a6b9c3b4a1e40293b1671"}, + {url = "https://files.pythonhosted.org/packages/fb/f4/c5d6d771e70ec7a9483a98054e8a5f386eda5b18b6c96544d251558c6c92/lazy_object_proxy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:9990d8e71b9f6488e91ad25f322898c136b008d87bf852ff65391b004da5e17b"}, + {url = "https://files.pythonhosted.org/packages/fc/8d/8e0fbfeec6e51184326e0886443e44142ce22d89fa9e9c3152900e654fa0/lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79a31b086e7e68b24b99b23d57723ef7e2c6d81ed21007b6281ebcd1688acb0a"}, + {url = "https://files.pythonhosted.org/packages/fe/bb/0fcc8585ffb7285df94382e20b54d54ca62a1bcf594f6f18d8feb3fc3b98/lazy_object_proxy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:db1c1722726f47e10e0b5fdbf15ac3b8adb58c091d12b3ab713965795036985f"}, +] +"line-profiler 4.0.3" = [ + {url = "https://files.pythonhosted.org/packages/00/83/4fcb0e1749980505968a0a4469c6fccf955d745b2779579c4b765ddbf87f/line_profiler-4.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:779a41bd7cceb5487abc1e985cf90bc0be7a61f369c32e9971e3b244153373da"}, + {url = "https://files.pythonhosted.org/packages/0d/ce/de6f05a09a6cc35db40dbed27e146e0d722590e920161bb7aa3beeceda0f/line_profiler-4.0.3-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c6a19e39be62aa7d849fab9a7f61591365b41ae87fbf4321de5442cb460f1fb5"}, + {url = "https://files.pythonhosted.org/packages/11/b7/bdd4e649fbe7c64458ee0745e39d135ad09a19fd399b6550fa7c9a065dce/line_profiler-4.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:48ce36c8fb17a64a494fb3ba0c591dd0fd2318bbe99c5c49da35f93257a5bc1a"}, + {url = "https://files.pythonhosted.org/packages/14/47/a44a0116c2689637056d2b20541bdee395ecc82f0bc3a9b138a0e014cff6/line_profiler-4.0.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:5a2654510d872e36c0737cc9358a94307c1db52bf906b3c92569c9bc067b896e"}, + {url = "https://files.pythonhosted.org/packages/14/4d/9e947e0f3b934b5b1076ff48f388835d4904891fc40a5a07033930849cbd/line_profiler-4.0.3-cp37-cp37m-win32.whl", hash = "sha256:d1bce3d49c8a0f89a04c41d95f256a48ee744d2cbca0c5fd859c928cddcccf3e"}, + {url = "https://files.pythonhosted.org/packages/1b/ee/3aac1fff88d08f78d70cfbca7a0267c52eebb57ba56586eac21eca1f8b36/line_profiler-4.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:b35795dc56dae57e1bca9d3ed7f03ca5ad86de578da29434dcb3fcc590009120"}, + {url = "https://files.pythonhosted.org/packages/22/5f/f7407a74c7fcbaec376e628904df7c7cc4625e82ea23aa1765946891c975/line_profiler-4.0.3-cp36-cp36m-win32.whl", hash = "sha256:7fa9bec2d79374e32441fa46d284e4241f73d5e23b91cb3286c5573c29c2f218"}, + {url = "https://files.pythonhosted.org/packages/22/d5/f8544aad54f7da906c725e486c5cea4e5eda7722e6d1a667268de09c51dd/line_profiler-4.0.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:0f281672166f7d403b927f3a8af1fa28125be6309d0e8a3910770037b5abc7be"}, + {url = "https://files.pythonhosted.org/packages/28/ee/6b9a52f6f1a6fe7a2819debc4a1f4baf95b93d14eade04432526ba2fec8d/line_profiler-4.0.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8ed41b4dc4bb5cea01e423928c50e354452eb1eb1b29b8b3ec94ee02b045fdf9"}, + {url = "https://files.pythonhosted.org/packages/39/3d/2a1ec556087b79ef1a69b39e36446cd8a504e3de98923ed009a9e42a83d1/line_profiler-4.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6eb244400492ffcbec0e6d5a52693828960a4c29f7d43c50190e4902bacad5be"}, + {url = "https://files.pythonhosted.org/packages/3e/b2/2b3c628a086ff28b0d53a5d5e71e096a58a296957928e7dc25bf6bf57c9e/line_profiler-4.0.3-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c60c0f5e14e17cf19d0f45dc25b406a47da57c667de6263281758fae0ec76ca0"}, + {url = "https://files.pythonhosted.org/packages/44/d7/341ee5578efc7ef5fd8cb3e435aaef06faf7342091b94413f6b76a4aca19/line_profiler-4.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e18ce062d652dc04eb0ebe5df13d78fb4d83979b459f8bca476059f3a71636d1"}, + {url = "https://files.pythonhosted.org/packages/4d/4d/d53d90128865620185df4fbe510d26bb3666f25a5cbcf941aae686573301/line_profiler-4.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52780098491df001a1315c1bc3d8199edd440698f1aef4e78875f9f2181f79bb"}, + {url = "https://files.pythonhosted.org/packages/53/b1/0299ca591433b49cfacedaec5027c8bb4f22a7f5997decf9537d88444a7b/line_profiler-4.0.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3873394ea9d66d05da6ed0f9f92e7463c44b716aadd034a603faad60a73577c6"}, + {url = "https://files.pythonhosted.org/packages/53/b5/1db2a0e229cd8e7f21c13144c3c98f4ec06bdcf7c6838ef2ab49c22e508b/line_profiler-4.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a727ddd521246fecd9a8aa918c81d2e7ebeef2c56af86be500280ec7ec720d1"}, + {url = "https://files.pythonhosted.org/packages/55/f6/1bb5683baa84339239ad904bff91d1e2294eb876654411419d76e6b1f670/line_profiler-4.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:6906259a2732c18f3f8c3f03cbe3899a640d4dd998d09a4c91d41140fd8bc686"}, + {url = "https://files.pythonhosted.org/packages/5a/8c/8859946e10c66de32e57efad07532326e953de6903dbbc3f3f562635ff7b/line_profiler-4.0.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f170232f15d48fb4e7ca46fe4147a54dd930baa7ef07c04c38b53e0e826028b8"}, + {url = "https://files.pythonhosted.org/packages/5b/29/ef4d8b44a44bbc85fbccbaae4f63e3ea5b52633263d6bb262493cdecd396/line_profiler-4.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:506ab549197844629834c5db4414517f474d862a90dc3920800f823db48e7601"}, + {url = "https://files.pythonhosted.org/packages/5d/01/ad719249dac1ddf339ff3b58fac3a3e2602c1d9f157fab093569a40d82a5/line_profiler-4.0.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4ae74a784e3c878bb52cb819a971315547cec2cab8705571318995c045aae27"}, + {url = "https://files.pythonhosted.org/packages/5e/cd/2f871cba5c94ebfc86b3b46ea98a9e182dde54e146bc9b96eb4abac79945/line_profiler-4.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af5259ba7f7ef73f9b02874fcfda2b0b7b0093e64b148bcf0d444bfb1d08fdcc"}, + {url = "https://files.pythonhosted.org/packages/62/f2/1bcf753c680be97c3388ae45b3873138b136f28e08075ffe4a11bb1e7335/line_profiler-4.0.3-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1fed42c6d070804d95990ad633f97778bc744f7569cb2b5a2cf5be05e932763"}, + {url = "https://files.pythonhosted.org/packages/64/8d/c55be9a76fe62dea5c24fb1984b72527500ebfe02a964d1d184a9ddd68cd/line_profiler-4.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:fdaac2e769e7d64cd3d19c4df5d26287e1cd362f47a1d3b42edd7c8420f40101"}, + {url = "https://files.pythonhosted.org/packages/6e/de/d9ee181fd40ab1f35dbaf6d4bf8003e6463c96a9dc0450b05a1428993d52/line_profiler-4.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c5b579c7d1b3661e56c63f5052f96c81b7453e503e0c2950df79776181cc8007"}, + {url = "https://files.pythonhosted.org/packages/7d/83/4e7350ed20014665d2301fd6d3ebb69673b6eb8a30d6d7225eec64d766bc/line_profiler-4.0.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ac886a51df9a5cec9dd9f483a63b88d1ecfff50151a9177f54931787e1c08575"}, + {url = "https://files.pythonhosted.org/packages/81/7f/824da144e670cdf9ad78708317cf612d73a517149d7a16fb007c5fd31866/line_profiler-4.0.3-cp38-cp38-win32.whl", hash = "sha256:b1ba5076d8cf9fc7e18bb79884915d78f856a9f03e999e9c25ace462c4745bcb"}, + {url = "https://files.pythonhosted.org/packages/9d/8f/1e538ef142f5a667f8e7198d3369f8ad1575e664efc88a8b3b49bdbf2017/line_profiler-4.0.3.tar.gz", hash = "sha256:deb2eb9e9119d911debe23edcec8ea68a2cd70c9e3f753c96aaf4a86ca497e7e"}, + {url = "https://files.pythonhosted.org/packages/9d/dc/51be1787688a744432eedf8d12e266b82d1e2ed2234bd9f33bd1a1524520/line_profiler-4.0.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1847946c78be769d3b053879bc2df6e7eed7800e2e3b35a297043d656b4bb2f9"}, + {url = "https://files.pythonhosted.org/packages/aa/b2/7e642307d15f88a7a5cc0f8d801d509a8a8fb31ec6ef22b059a89c05fd5e/line_profiler-4.0.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e9f56be58b78bcfdc505987730b1a0099f8b2693c392879d0a8d1dd81a437d0"}, + {url = "https://files.pythonhosted.org/packages/ab/85/6aad2a06a068e91e66b9cddc9388db2bde592a07832015bfec9d6910c41c/line_profiler-4.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:659c3f99359825034a5becb7de2e19eeee96bbe60fface73059b446124b942d4"}, + {url = "https://files.pythonhosted.org/packages/af/4b/2d12233e9c0810064d19a45037961282581b9df859ae280ea77026ae783e/line_profiler-4.0.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:5e9c5c6ea82ad587ebe127a1f18b37634ce9e2d8b2065c2cb382dc5576551503"}, + {url = "https://files.pythonhosted.org/packages/b5/c9/745a2519456c0c75427c9e0f385cae579e58122230096996e9da28067984/line_profiler-4.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db98ff49c1f4753959bb1e9b9835626cb817d1add6d480311938c373e9c4c5f7"}, + {url = "https://files.pythonhosted.org/packages/b7/75/50b01616ef62595f51a6c92b1dda5928124850277cd248fe80cc07c1f975/line_profiler-4.0.3-cp39-cp39-win32.whl", hash = "sha256:9e7fbe5280927d1c647b43516aedc2f21b0bfad27f6bc531ebca9df7c77f2f7f"}, + {url = "https://files.pythonhosted.org/packages/b8/0a/6d872885cbd2050d646299505c0e8e20a9caceb4c43d42187998db95e8f3/line_profiler-4.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:eb1d5e90862ac5385fdb002c40fe45bbf0396025dabc0565ac97efc622122274"}, + {url = "https://files.pythonhosted.org/packages/bd/46/27960258a2fdd42d26acf34e61045c9d234e81e9a88bd419dff1ca98129f/line_profiler-4.0.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fbe0036a306835978270a66c460c7b57869fe985ca620613321971d396de295f"}, + {url = "https://files.pythonhosted.org/packages/bd/fa/ef7e1944af3ab3ed4e4c54fe96c958d8857efab341a46b3020b96932809d/line_profiler-4.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:878479d3df35f6a3be83cb7ea5ee3df8f51003da6eca291242ebfecaf8cf940f"}, + {url = "https://files.pythonhosted.org/packages/c1/c8/de3a076bacaa587db00ebf5bcf52ea6b99acf8373c9cfffc6fc9e18fb45d/line_profiler-4.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a3337db24f51bda9f7c2fc5a135fc657c5cc818ba5905195a4f79f7489048bae"}, + {url = "https://files.pythonhosted.org/packages/c2/87/d93e0a854e0173e7e841277ffd6aac518092b4f59a6abf95ddb3258b26db/line_profiler-4.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0a5dad6fa4ebc70676574941a564cdae4e664bf54fd68a8f19799167a927a3db"}, + {url = "https://files.pythonhosted.org/packages/c5/cf/06ff7ccc949891ef1af6b85dd97f7721c3ec0e1a8c92accb25aa4cf406d1/line_profiler-4.0.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9bc4bf53a2c79c935a5e59645a6f5d9cc8618a4aded0d2116db5d4ebecab6dae"}, + {url = "https://files.pythonhosted.org/packages/c6/71/892c14fbf3a8e138d329a125b6db55a449f59e57fb60726ed4a4da0f3e83/line_profiler-4.0.3-cp310-cp310-win32.whl", hash = "sha256:a4b7e84d800bb466e461d827eaadbf0bce1476b76a29b92d24f524db028ae4e1"}, + {url = "https://files.pythonhosted.org/packages/c6/d2/3754343a9cd2ce6b86ead5a64d44e4080a877f5f5f4d34b47bc63a5da6ae/line_profiler-4.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:81404b2530e2f4cb0e69f8b624957caef2b313227380e6aa7d3ccef494941f91"}, + {url = "https://files.pythonhosted.org/packages/c7/49/2239793edb27e2fa6cdafe97921ded66c38fcc8278722725d1b22d491b74/line_profiler-4.0.3-cp311-cp311-win32.whl", hash = "sha256:467de51ae6f154865f40e7d645462c8bbf9dedb6c432b1af173c099d79b81c2e"}, + {url = "https://files.pythonhosted.org/packages/f3/3b/ec2b5037a3ceddc8680349ad5cf0561e794afa88b23d8a25cd38c1841beb/line_profiler-4.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12bf7dc576707760d58efb221f4ee36cc9ec3e514733186c807fe6839c65a9e6"}, + {url = "https://files.pythonhosted.org/packages/f8/43/3a44d1f3f4735ceb06ed826ea796fe8c1d0cac9a304eaba6f94579787cbb/line_profiler-4.0.3-cp36-cp36m-win_amd64.whl", hash = "sha256:0783704f6bdd6d1029c193bb270b9e540f5b97ded662c74885b609d4bc016bfa"}, +] +"mako 1.2.4" = [ + {url = "https://files.pythonhosted.org/packages/03/3b/68690a035ba7347860f1b8c0cde853230ba69ff41df5884ea7d89fe68cd3/Mako-1.2.4-py3-none-any.whl", hash = "sha256:c97c79c018b9165ac9922ae4f32da095ffd3c4e6872b45eded42926deea46818"}, + {url = "https://files.pythonhosted.org/packages/05/5f/2ba6e026d33a0e6ddc1dddf9958677f76f5f80c236bd65309d280b166d3e/Mako-1.2.4.tar.gz", hash = "sha256:d60a3903dc3bb01a18ad6a89cdbe2e4eadc69c0bc8ef1e3773ba53d44c3f7a34"}, +] +"markdown-it-py 3.0.0" = [ + {url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] +"markupsafe 2.1.3" = [ + {url = "https://files.pythonhosted.org/packages/03/06/e72e88f81f8c91d4f488d21712d2d403fd644e3172eaadc302094377bc22/MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, + {url = "https://files.pythonhosted.org/packages/03/65/3473d2cb84bb2cda08be95b97fc4f53e6bcd701a2d50ba7b7c905e1e9273/MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, + {url = "https://files.pythonhosted.org/packages/10/b3/c2b0a61cc0e1d50dd8a1b663ba4866c667cb58fb35f12475001705001680/MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, + {url = "https://files.pythonhosted.org/packages/12/b3/d9ed2c0971e1435b8a62354b18d3060b66c8cb1d368399ec0b9baa7c0ee5/MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, + {url = "https://files.pythonhosted.org/packages/20/1d/713d443799d935f4d26a4f1510c9e61b1d288592fb869845e5cc92a1e055/MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, + {url = "https://files.pythonhosted.org/packages/22/81/b5659e2b6ae1516495a22f87370419c1d79c8d853315e6cbe5172fc01a06/MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, + {url = "https://files.pythonhosted.org/packages/32/d4/ce98c4ca713d91c4a17c1a184785cc00b9e9c25699d618956c2b9999500a/MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, + {url = "https://files.pythonhosted.org/packages/3c/c8/74d13c999cbb49e3460bf769025659a37ef4a8e884de629720ab4e42dcdb/MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, + {url = "https://files.pythonhosted.org/packages/43/70/f24470f33b2035b035ef0c0ffebf57006beb2272cf3df068fc5154e04ead/MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, + {url = "https://files.pythonhosted.org/packages/43/ad/7246ae594aac948b17408c0ff0f9ff0bc470bdbe9c672a754310db64b237/MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, + {url = "https://files.pythonhosted.org/packages/44/53/93405d37bb04a10c43b1bdd6f548097478d494d7eadb4b364e3e1337f0cc/MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, + {url = "https://files.pythonhosted.org/packages/47/26/932140621773bfd4df3223fbdd9e78de3477f424f0d2987c313b1cb655ff/MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, + {url = "https://files.pythonhosted.org/packages/4d/e4/77bb622d6a37aeb51ee55857100986528b7f47d6dbddc35f9b404622ed50/MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, + {url = "https://files.pythonhosted.org/packages/4f/13/cf36eff21600fb21d5bd8c4c1b6ff0b7cc0ff37b955017210cfc6f367972/MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, + {url = "https://files.pythonhosted.org/packages/62/9b/4908a57acf39d8811836bc6776b309c2e07d63791485589acf0b6d7bc0c6/MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, + {url = "https://files.pythonhosted.org/packages/68/8d/c33c43c499c19f4b51181e196c9a497010908fc22c5de33551e298aa6a21/MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, + {url = "https://files.pythonhosted.org/packages/6a/86/654dc431513cd4417dfcead8102f22bece2d6abf2f584f0e1cc1524f7b94/MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, + {url = "https://files.pythonhosted.org/packages/6d/7c/59a3248f411813f8ccba92a55feaac4bf360d29e2ff05ee7d8e1ef2d7dbf/MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, + {url = "https://files.pythonhosted.org/packages/71/61/f5673d7aac2cf7f203859008bb3fc2b25187aa330067c5e9955e5c5ebbab/MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, + {url = "https://files.pythonhosted.org/packages/74/a3/54fc60ee2da3ab6d68b1b2daf4897297c597840212ee126e68a4eb89fcd7/MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, + {url = "https://files.pythonhosted.org/packages/7d/48/6ba4db436924698ca22109325969e00be459d417830dafec3c1001878b57/MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, + {url = "https://files.pythonhosted.org/packages/84/a8/c4aebb8a14a1d39d5135eb8233a0b95831cdc42c4088358449c3ed657044/MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, + {url = "https://files.pythonhosted.org/packages/8b/bb/72ca339b012054a84753accabe3258e0baf6e34bd0ab6e3670b9a65f679d/MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, + {url = "https://files.pythonhosted.org/packages/8d/66/4a46c7f1402e0377a8b220fd4b53cc4f1b2337ab0d97f06e23acd1f579d1/MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, + {url = "https://files.pythonhosted.org/packages/96/e4/4db3b1abc5a1fe7295aa0683eafd13832084509c3b8236f3faf8dd4eff75/MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, + {url = "https://files.pythonhosted.org/packages/9b/c1/9f44da5ca74f95116c644892152ca6514ecdc34c8297a3f40d886147863d/MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, + {url = "https://files.pythonhosted.org/packages/a2/b2/624042cb58cc6b3529a6c3a7b7d230766e3ecb768cba118ba7befd18ed6f/MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, + {url = "https://files.pythonhosted.org/packages/a2/f7/9175ad1b8152092f7c3b78c513c1bdfe9287e0564447d1c2d3d1a2471540/MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, + {url = "https://files.pythonhosted.org/packages/a6/56/f1d4ee39e898a9e63470cbb7fae1c58cce6874f25f54220b89213a47f273/MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, + {url = "https://files.pythonhosted.org/packages/a8/12/fd9ef3e09a7312d60467c71037283553ff2acfcd950159cd4c3ca9558af4/MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, + {url = "https://files.pythonhosted.org/packages/ab/20/f59423543a8422cb8c69a579ebd0ef2c9dafa70cc8142b7372b5b4073caa/MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, + {url = "https://files.pythonhosted.org/packages/b2/0d/cbaade3ee8efbd5ce2fb72b48cc51479ebf3d4ce2c54dcb6557d3ea6a950/MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, + {url = "https://files.pythonhosted.org/packages/b2/27/07e5aa9f93314dc65ad2ad9b899656dee79b70a9425ee199dd5a4c4cf2cd/MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, + {url = "https://files.pythonhosted.org/packages/bb/82/f88ccb3ca6204a4536cf7af5abdad7c3657adac06ab33699aa67279e0744/MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, + {url = "https://files.pythonhosted.org/packages/be/bb/08b85bc194034efbf572e70c3951549c8eca0ada25363afc154386b5390a/MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {url = "https://files.pythonhosted.org/packages/bf/b7/c5ba9b7ad9ad21fc4a60df226615cf43ead185d328b77b0327d603d00cc5/MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, + {url = "https://files.pythonhosted.org/packages/c0/c7/171f5ac6b065e1425e8fabf4a4dfbeca76fd8070072c6a41bd5c07d90d8b/MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, + {url = "https://files.pythonhosted.org/packages/c9/80/f08e782943ee7ae6e9438851396d00a869f5b50ea8c6e1f40385f3e95771/MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, + {url = "https://files.pythonhosted.org/packages/d2/a1/4ae49dd1520c7b891ea4963258aab08fb2554c564781ecb2a9c4afdf9cb1/MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, + {url = "https://files.pythonhosted.org/packages/d5/c1/1177f712d4ab91eb67f79d763a7b5f9c5851ee3077d6b4eee15e23b6b93e/MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, + {url = "https://files.pythonhosted.org/packages/de/63/cb7e71984e9159ec5f45b5e81e896c8bdd0e45fe3fc6ce02ab497f0d790e/MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, + {url = "https://files.pythonhosted.org/packages/de/e2/32c14301bb023986dff527a49325b6259cab4ebb4633f69de54af312fc45/MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, + {url = "https://files.pythonhosted.org/packages/e5/dd/49576e803c0d974671e44fa78049217fcc68af3662a24f831525ed30e6c7/MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, + {url = "https://files.pythonhosted.org/packages/e6/5c/8ab8f67bbbbf90fe88f887f4fa68123435c5415531442e8aefef1e118d5c/MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, + {url = "https://files.pythonhosted.org/packages/f4/a0/103f94793c3bf829a18d2415117334ece115aeca56f2df1c47fa02c6dbd6/MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, + {url = "https://files.pythonhosted.org/packages/f7/9c/86cbd8e0e1d81f0ba420f20539dd459c50537c7751e28102dbfee2b6f28c/MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, + {url = "https://files.pythonhosted.org/packages/f8/33/e9e83b214b5f8d9a60b26e60051734e7657a416e5bce7d7f1c34e26badad/MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, + {url = "https://files.pythonhosted.org/packages/fa/bb/12fb5964c4a766eb98155dd31ec070adc8a69a395564ffc1e7b34d91335a/MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, + {url = "https://files.pythonhosted.org/packages/fe/09/c31503cb8150cf688c1534a7135cc39bb9092f8e0e6369ec73494d16ee0e/MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, + {url = "https://files.pythonhosted.org/packages/fe/21/2eff1de472ca6c99ec3993eab11308787b9879af9ca8bbceb4868cf4f2ca/MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, +] +"mccabe 0.7.0" = [ + {url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] +"mdurl 0.1.2" = [ + {url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] +"msgpack 1.0.5" = [ + {url = "https://files.pythonhosted.org/packages/0a/04/bc319ba061f6dc9077745988be288705b3f9f18c5a209772a3e8fcd419fd/msgpack-1.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9985b214f33311df47e274eb788a5893a761d025e2b92c723ba4c63936b69b1"}, + {url = "https://files.pythonhosted.org/packages/0d/90/44edef4a8c6f035b054c4b017c5adcb22a35ec377e17e50dd5dced279a6b/msgpack-1.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:916723458c25dfb77ff07f4c66aed34e47503b2eb3188b3adbec8d8aa6e00f48"}, + {url = "https://files.pythonhosted.org/packages/0e/69/3d10e741dd2bbb806af5cdc76551735baab5f5f9773701eb05502c913a6e/msgpack-1.0.5-cp311-cp311-win32.whl", hash = "sha256:c396e2cc213d12ce017b686e0f53497f94f8ba2b24799c25d913d46c08ec422c"}, + {url = "https://files.pythonhosted.org/packages/10/ca/50c3a5e92d459a942169747315afd8c226d05427eccff903ddf33135c574/msgpack-1.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20a97bf595a232c3ee6d57ddaadd5453d174a52594bf9c21d10407e2a2d9b3bd"}, + {url = "https://files.pythonhosted.org/packages/10/fe/9e004c4deb457f1ef1ad88c1188da5691ff1855e0d03a5ac3635ae1f6530/msgpack-1.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:55b56a24893105dc52c1253649b60f475f36b3aa0fc66115bffafb624d7cb30b"}, + {url = "https://files.pythonhosted.org/packages/12/6e/0cfd1dc07f61a6ac606587a393f489c3ca463469d285a73c8e5e2f61b021/msgpack-1.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:20c784e66b613c7f16f632e7b5e8a1651aa5702463d61394671ba07b2fc9e025"}, + {url = "https://files.pythonhosted.org/packages/17/10/be97811782473d709d07b65a3955a5a76d47686aff3d62bb41d48aea7c92/msgpack-1.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:362d9655cd369b08fda06b6657a303eb7172d5279997abe094512e919cf74b11"}, + {url = "https://files.pythonhosted.org/packages/18/3f/3860151fbdf50e369bbe4ffd307a588417669c725025e383f3ce5893690f/msgpack-1.0.5-cp39-cp39-win32.whl", hash = "sha256:ac9dd47af78cae935901a9a500104e2dea2e253207c924cc95de149606dc43cc"}, + {url = "https://files.pythonhosted.org/packages/19/0c/2c3b443df88f5d400f2e19a3d867564d004b26e137f18c2f2663913987bc/msgpack-1.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:476a8fe8fae289fdf273d6d2a6cb6e35b5a58541693e8f9f019bfe990a51e4ba"}, + {url = "https://files.pythonhosted.org/packages/1a/f7/df5814697c25bdebb14ea97d27ddca04f5d4c6e249f096d086fea521c139/msgpack-1.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b72d0698f86e8d9ddf9442bdedec15b71df3598199ba33322d9711a19f08145c"}, + {url = "https://files.pythonhosted.org/packages/27/ad/4edfe383ec3185611441179ffee8cbc8155d7575fbad73f6d31015e35451/msgpack-1.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9f5ae84c5c8a857ec44dc180a8b0cc08238e021f57abdf51a8182e915e6299f0"}, + {url = "https://files.pythonhosted.org/packages/28/8f/c58c53c884217cc572c19349c7e1129b5a6eae36df0a017aae3a8f3d7aa8/msgpack-1.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:addab7e2e1fcc04bd08e4eb631c2a90960c340e40dfc4a5e24d2ff0d5a3b3edb"}, + {url = "https://files.pythonhosted.org/packages/29/56/1fb6b96aab759ab3bc05b03ba6d936b350db72aac203cde56ea6bd001237/msgpack-1.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d25dd59bbbbb996eacf7be6b4ad082ed7eacc4e8f3d2df1ba43822da9bfa122a"}, + {url = "https://files.pythonhosted.org/packages/2b/c4/f2c8695ae69d1425eddc5e2f849c525b562dc8409bc2979e525f3dd4fecd/msgpack-1.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56a62ec00b636583e5cb6ad313bbed36bb7ead5fa3a3e38938503142c72cba4f"}, + {url = "https://files.pythonhosted.org/packages/2b/d4/9165cf113f9b86ce55e36f36bc6cd9e0c5601b0ade02741b2ead8b5dc254/msgpack-1.0.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a2b031c2e9b9af485d5e3c4520f4220d74f4d222a5b8dc8c1a3ab9448ca79c57"}, + {url = "https://files.pythonhosted.org/packages/2c/e9/c79ecc36cfa34d850a01773565e0fccafd69efff07172028c3a5f758b83f/msgpack-1.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b2de4c1c0538dcb7010902a2b97f4e00fc4ddf2c8cda9749af0e594d3b7fa3d7"}, + {url = "https://files.pythonhosted.org/packages/2f/21/e488871f8e498efe14821b0c870eb95af52cfafb9b8dd41d83fad85b383b/msgpack-1.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48296af57cdb1d885843afd73c4656be5c76c0c6328db3440c9601a98f303d87"}, + {url = "https://files.pythonhosted.org/packages/33/0a/aa7b53ae17cf1dc1c352d705ab3162fc572c55048cc3177c1a88009c47fd/msgpack-1.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ab2f3331cb1b54165976a9d976cb251a83183631c88076613c6c780f0d6e45a"}, + {url = "https://files.pythonhosted.org/packages/33/52/099f0dde1283bac7bf267ab941dfa3b7c89ee701e4252973f8d3c10e68d6/msgpack-1.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:379026812e49258016dd84ad79ac8446922234d498058ae1d415f04b522d5b2d"}, + {url = "https://files.pythonhosted.org/packages/34/3c/34e94b091b3fdf941dbce5bc619e2fa5488d49fdf00944b50f5a1d6e1871/msgpack-1.0.5-cp36-cp36m-win32.whl", hash = "sha256:b5ef2f015b95f912c2fcab19c36814963b5463f1fb9049846994b007962743e9"}, + {url = "https://files.pythonhosted.org/packages/3c/e5/3d436bed11849ba05d777ed3fd1a0440170bad460335ea541dd6946047ed/msgpack-1.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:4867aa2df9e2a5fa5f76d7d5565d25ec76e84c106b55509e78c1ede0f152659a"}, + {url = "https://files.pythonhosted.org/packages/3e/80/bc7fdb75a35bf32c7c529c247dcadfd0502aac2309e207a89b0be6fe42ea/msgpack-1.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cb47c21a8a65b165ce29f2bec852790cbc04936f502966768e4aae9fa763cb7"}, + {url = "https://files.pythonhosted.org/packages/43/87/6507d56f62b958d822ae4ffe1c4507ed7d3cf37ad61114665816adcf4adc/msgpack-1.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a740fa0e4087a734455f0fc3abf5e746004c9da72fbd541e9b113013c8dc3282"}, + {url = "https://files.pythonhosted.org/packages/45/85/6b55b0cabad846d3e730226a897f878f8f63ee505668bb6c55a697b0bfb0/msgpack-1.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bf22a83f973b50f9d38e55c6aade04c41ddda19b00c4ebc558930d78eecc64ed"}, + {url = "https://files.pythonhosted.org/packages/45/e1/6408389bd2cf0c339ea317926beb64d100f60bc8d236ac59f1c1162be2e4/msgpack-1.0.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1d46dfe3832660f53b13b925d4e0fa1432b00f5f7210eb3ad3bb9a13c6204a6"}, + {url = "https://files.pythonhosted.org/packages/49/57/a28120d82f8e77622a1e1efc652389c71145f6b89b47b39814a7c6038373/msgpack-1.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57e1f3528bd95cc44684beda696f74d3aaa8a5e58c816214b9046512240ef437"}, + {url = "https://files.pythonhosted.org/packages/4b/3d/cc5eb6d69e0ecde80a78cc42f48579971ec333e509d56a4a6de1a2c40ba2/msgpack-1.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:266fa4202c0eb94d26822d9bfd7af25d1e2c088927fe8de9033d929dd5ba24c5"}, + {url = "https://files.pythonhosted.org/packages/56/50/bfcc0fad07067b6f1b09d940272ec749d5fe82570d938c2348c3ad0babf7/msgpack-1.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:821c7e677cc6acf0fd3f7ac664c98803827ae6de594a9f99563e48c5a2f27eb0"}, + {url = "https://files.pythonhosted.org/packages/59/67/f992ada3b42889f1b984e5651d63ea21ca3a92049cff6d75fe0a4a63e422/msgpack-1.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e42b9594cc3bf4d838d67d6ed62b9e59e201862a25e9a157019e171fbe672dd3"}, + {url = "https://files.pythonhosted.org/packages/60/bc/af94acdebc26b8d92d5673d20529438aa225698dc23338fb43c875c8968e/msgpack-1.0.5-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:f933bbda5a3ee63b8834179096923b094b76f0c7a73c1cfe8f07ad608c58844b"}, + {url = "https://files.pythonhosted.org/packages/62/57/170af6c6fccd2d950ea01e1faa58cae9643226fa8705baded11eca3aa8b5/msgpack-1.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c05a4a96585525916b109bb85f8cb6511db1c6f5b9d9cbcbc940dc6b4be944b"}, + {url = "https://files.pythonhosted.org/packages/62/5c/9c7fed4ca0235a2d7b8d15b4047c328976b97d2b227719e54cad1e47c244/msgpack-1.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef8108f8dedf204bb7b42994abf93882da1159728a2d4c5e82012edd92c9da9f"}, + {url = "https://files.pythonhosted.org/packages/67/f8/e3ab674f4a945308362e9342297fe6b35a89dd0f648aa325aabffa5dc210/msgpack-1.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3055b0455e45810820db1f29d900bf39466df96ddca11dfa6d074fa47054376d"}, + {url = "https://files.pythonhosted.org/packages/6b/6d/de239d77d347f1990c41b4800075a15e06f748186dd120166270dd071734/msgpack-1.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28592e20bbb1620848256ebc105fc420436af59515793ed27d5c77a217477705"}, + {url = "https://files.pythonhosted.org/packages/6b/79/0dec8f035160464ca88b221cc79691a71cf88dc25207c17f1d918b2c7bb0/msgpack-1.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c4c68d87497f66f96d50142a2b73b97972130d93677ce930718f68828b382e2"}, + {url = "https://files.pythonhosted.org/packages/6c/fa/3ca00fb1e53bcacf8c186fa6aff2d2086862b12e289bcf38227d9d40bd86/msgpack-1.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:06f5174b5f8ed0ed919da0e62cbd4ffde676a374aba4020034da05fab67b9164"}, + {url = "https://files.pythonhosted.org/packages/6c/fe/8a7747ca57074307a2e8f1de58441952a9dbdf9e8a8e5873d53a5ce0835c/msgpack-1.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5494ea30d517a3576749cad32fa27f7585c65f5f38309c88c6d137877fa28a5a"}, + {url = "https://files.pythonhosted.org/packages/72/ac/2eda5af7cd1450c52d031e48c76b280eac5bb2e588678876612f95be34ab/msgpack-1.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e57916ef1bd0fee4f21c4600e9d1da352d8816b52a599c46460e93a6e9f17086"}, + {url = "https://files.pythonhosted.org/packages/73/99/f338ce8b69e934c04e5d9187f85de1ae395882cd56e7deb48e78a1749af8/msgpack-1.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdc793c50be3f01106245a61b739328f7dccc2c648b501e237f0699fe1395b81"}, + {url = "https://files.pythonhosted.org/packages/7b/e9/b47f9e93fc381885624c40cbbbd0480b18ae11ca588162fe724d43495372/msgpack-1.0.5-cp310-cp310-win32.whl", hash = "sha256:382b2c77589331f2cb80b67cc058c00f225e19827dbc818d700f61513ab47bea"}, + {url = "https://files.pythonhosted.org/packages/7e/1c/9d0fd241a4e88e1cd2f5babea4a27ac25b1b86dbbc05fa10741e82079a93/msgpack-1.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe5c63197c55bce6385d9aee16c4d0641684628f63ace85f73571e65ad1c1e8d"}, + {url = "https://files.pythonhosted.org/packages/80/f0/c1fadb4e4a38fda19e35b1b6f887d72cc9c57778af43b53f64a8cd62e922/msgpack-1.0.5-cp38-cp38-win32.whl", hash = "sha256:1c0f7c47f0087ffda62961d425e4407961a7ffd2aa004c81b9c07d9269512f6e"}, + {url = "https://files.pythonhosted.org/packages/95/c9/560c3203c4327881c9f2de26c42dacdd9567bfe7fa43458e2a680c4bdcaf/msgpack-1.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1967f6129fc50a43bfe0951c35acbb729be89a55d849fab7686004da85103f1c"}, + {url = "https://files.pythonhosted.org/packages/9a/0b/ea8a49d24654f9e8604ea78b80a4d7b0cc31817d8fb6987001223ae7feaf/msgpack-1.0.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:36961b0568c36027c76e2ae3ca1132e35123dcec0706c4b7992683cc26c1320c"}, + {url = "https://files.pythonhosted.org/packages/9f/4a/36d936e54cf71e23ad276564465f6a54fb129e3d61520b76e13e0bb29167/msgpack-1.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:525228efd79bb831cf6830a732e2e80bc1b05436b086d4264814b4b2955b2fa9"}, + {url = "https://files.pythonhosted.org/packages/a2/e0/f3d5dd7809cf5728bb1bae683032ce50547d009be6551054815a8bf2a2da/msgpack-1.0.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4c075728a1095efd0634a7dccb06204919a2f67d1893b6aa8e00497258bf926c"}, + {url = "https://files.pythonhosted.org/packages/ab/ff/ca74e519c47139b6c08fb21db5ead2bd2eed6cb1225f9be69390cdb48182/msgpack-1.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:586d0d636f9a628ddc6a17bfd45aa5b5efaf1606d2b60fa5d87b8986326e933f"}, + {url = "https://files.pythonhosted.org/packages/b8/bc/1d5fe4732dc78ff86aaf677596da08f0ae736e60ca8ab49c1f1c7366cb1a/msgpack-1.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed40e926fa2f297e8a653c954b732f125ef97bdd4c889f243182299de27e2aa9"}, + {url = "https://files.pythonhosted.org/packages/bf/68/032e62ad44f92ba6a4ae7c45054843cdec7f0c405ecdfd166f25123b0c47/msgpack-1.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18334484eafc2b1aa47a6d42427da7fa8f2ab3d60b674120bce7a895a0a85bdd"}, + {url = "https://files.pythonhosted.org/packages/c1/57/01f2d8805160f559ec21d095fc7576a26fbaed2475af24ce4a135c380c14/msgpack-1.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e6ca5d5699bcd89ae605c150aee83b5321f2115695e741b99618f4856c50898"}, + {url = "https://files.pythonhosted.org/packages/c2/3b/70d1eaaafb451679663a72164c46fadfb93f59c90f584dcd77289f90e4c5/msgpack-1.0.5-cp36-cp36m-win_amd64.whl", hash = "sha256:288e32b47e67f7b171f86b030e527e302c91bd3f40fd9033483f2cacc37f327a"}, + {url = "https://files.pythonhosted.org/packages/c5/c1/1b591574ba71481fbf38359a8fca5108e4ad130a6dbb9b2acb3e9277d0fe/msgpack-1.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:137850656634abddfb88236008339fdaba3178f4751b28f270d2ebe77a563b6c"}, + {url = "https://files.pythonhosted.org/packages/ce/b8/89cb1809b076a4651169851aa1f98128b75cbfe14034b914c9040b13c4cf/msgpack-1.0.5-cp37-cp37m-win32.whl", hash = "sha256:cb5aaa8c17760909ec6cb15e744c3ebc2ca8918e727216e79607b7bbce9c8f77"}, + {url = "https://files.pythonhosted.org/packages/d3/32/9b7a2dba9485dd7d201e4e00638fbf86e0d535a91653889c5b4dc813efdf/msgpack-1.0.5-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:366c9a7b9057e1547f4ad51d8facad8b406bab69c7d72c0eb6f529cf76d4b85f"}, + {url = "https://files.pythonhosted.org/packages/da/46/855bdcbf004fd87b6a4451e8dcd61329439dcd9039887f71ca5085769216/msgpack-1.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:bae7de2026cbfe3782c8b78b0db9cbfc5455e079f1937cb0ab8d133496ac55e1"}, + {url = "https://files.pythonhosted.org/packages/dc/a1/eba11a0d4b764bc62966a565b470f8c6f38242723ba3057e9b5098678c30/msgpack-1.0.5.tar.gz", hash = "sha256:c075544284eadc5cddc70f4757331d99dcbc16b2bbd4849d15f8aae4cf36d31c"}, + {url = "https://files.pythonhosted.org/packages/e8/1f/be19c9c9cfdcc2ae8ee8c65dbe5f281cc1f3331f9b9523735f39b090b448/msgpack-1.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:ab31e908d8424d55601ad7075e471b7d0140d4d3dd3272daf39c5c19d936bd82"}, + {url = "https://files.pythonhosted.org/packages/e8/60/78906f564804aae23eb1102eca8b8830f1e08a649c179774c05fa7dc0aad/msgpack-1.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:17358523b85973e5f242ad74aa4712b7ee560715562554aa2134d96e7aa4cbbf"}, + {url = "https://files.pythonhosted.org/packages/e9/f1/45b73a9e97f702bcb5f51569b93990e456bc969363e55122374c22ed7d24/msgpack-1.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a61215eac016f391129a013c9e46f3ab308db5f5ec9f25811e811f96962599a8"}, + {url = "https://files.pythonhosted.org/packages/ef/13/c110d89d5079169354394dc226e6f84d818722939bc1fe3f9c25f982e903/msgpack-1.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1835c84d65f46900920b3708f5ba829fb19b1096c1800ad60bae8418652a951d"}, + {url = "https://files.pythonhosted.org/packages/f1/1f/cc3e8274934c8323f6106dae22cba8bad413166f4efb3819573de58c215c/msgpack-1.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:332360ff25469c346a1c5e47cbe2a725517919892eda5cfaffe6046656f0b7bb"}, + {url = "https://files.pythonhosted.org/packages/f2/da/770118f8d48e11cc9a2c7cb60d7d3c8016266526bd42c6ff5bd21013d099/msgpack-1.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4f8d8b3bf1ff2672567d6b5c725a1b347fe838b912772aa8ae2bf70338d5a198"}, + {url = "https://files.pythonhosted.org/packages/f5/80/ef9c31210ac580163c0de2db4fb3179c6a3f1228c18fd366280e01d9e5d2/msgpack-1.0.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f837b93669ce4336e24d08286c38761132bc7ab29782727f8557e1eb21b2080"}, +] +"multidict 6.0.4" = [ + {url = "https://files.pythonhosted.org/packages/00/bb/1cdffe9b1ab01830bc9255a64524c34b71c20a4affe5d1000b223a41698d/multidict-6.0.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d"}, + {url = "https://files.pythonhosted.org/packages/0a/a1/a0446805d76fd6ada6de501c90520c963f8b5bf1f5a7a75ad80ba076897d/multidict-6.0.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87"}, + {url = "https://files.pythonhosted.org/packages/0c/ff/342e4f8f1c83fb2bdbca067a78cb88e80a0b93aab5443c8095daa97bf94b/multidict-6.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf"}, + {url = "https://files.pythonhosted.org/packages/17/3d/081e3f2c4c6b65e6347b5a4ed465fb36041f52c2dad1ad3178ad837c4f0d/multidict-6.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11"}, + {url = "https://files.pythonhosted.org/packages/1a/3f/35c77a24a68ea1406a4d11e409e54c88eaf92afe4f7613b581d625ed812f/multidict-6.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52"}, + {url = "https://files.pythonhosted.org/packages/1a/5a/e31fc5799b6d8929da4db92cc166d9257e7f85b4d6c7245143c0dae29413/multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"}, + {url = "https://files.pythonhosted.org/packages/1b/7c/705e0f14225a748b0729d97095283b2251dbf7cada28bfe75a11b7cf2d0c/multidict-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e"}, + {url = "https://files.pythonhosted.org/packages/24/d1/56b6d5eb964161c55a8a7ad53fe4c93a694e44d04fd1405f3c1b98de5627/multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"}, + {url = "https://files.pythonhosted.org/packages/25/1f/b10a0abdfc33069b6c92935cff81b97dd7d034149b05025a92326972b371/multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"}, + {url = "https://files.pythonhosted.org/packages/27/81/2502174a4988981a33bb3458b9d5a14495b1e3e45c36ca234f75115d4723/multidict-6.0.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac"}, + {url = "https://files.pythonhosted.org/packages/27/ce/2207d548200d42c3a0b3cb11b8957f4d29f82f95977ae2cc8276a7d719e5/multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"}, + {url = "https://files.pythonhosted.org/packages/28/a3/db4511fbc4bf75a6c0afea0f009605432561ce0bd2b4fddc2047e9cb0b6b/multidict-6.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449"}, + {url = "https://files.pythonhosted.org/packages/2a/7b/6900273aec2eef33e17094407b67dca697ceeb75e3ddb86cccbdafb46e4b/multidict-6.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d"}, + {url = "https://files.pythonhosted.org/packages/2f/38/e0514ddb9b454b06fc8b29eb8b45ae1861cf1850acc2b0f01ad38b047ad3/multidict-6.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664"}, + {url = "https://files.pythonhosted.org/packages/3c/b3/1c8b525a7243c395a73d0ba35f4625333315c5261d01acc3bcde852a9548/multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"}, + {url = "https://files.pythonhosted.org/packages/3c/ee/7b419645f86d43ae393f2451bc95287aec2e7539e93af619b280aeda9b04/multidict-6.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063"}, + {url = "https://files.pythonhosted.org/packages/3d/22/35539dddb1971eb8dc88bb19d22d636eb9efe1ad7549d2f319a7951cbbe7/multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"}, + {url = "https://files.pythonhosted.org/packages/42/b6/61cb83e174e77e4e2607f60f26ff8e975eb7961e143fa01682b8c3acb201/multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"}, + {url = "https://files.pythonhosted.org/packages/46/d2/0b1e4e8ad7097dc12543571333d65580e918dd19e26109dc4b8ec13b744c/multidict-6.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95"}, + {url = "https://files.pythonhosted.org/packages/47/76/fe01957664719f8b02bd4930b2f95e4f4a3ffaca42c9f21db92a5de4156e/multidict-6.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f"}, + {url = "https://files.pythonhosted.org/packages/47/a4/69f7255bc1398caa2c1eecf4c937d3ed6ae483327f39f8b1115b578905bb/multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"}, + {url = "https://files.pythonhosted.org/packages/47/e4/745fb4cc79b439b1c1d1f441f2aa65f6250b77052d2bf4d8d8b5970ee672/multidict-6.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9"}, + {url = "https://files.pythonhosted.org/packages/4a/15/bd620f7a6eb9aa5112c4ef93e7031bcd071e0611763d8e17706ef8ba65e0/multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, + {url = "https://files.pythonhosted.org/packages/4d/1f/83656180657d0d359b12866b9af77dbb58f46cb5f454301d2c37ec97a9e1/multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"}, + {url = "https://files.pythonhosted.org/packages/56/b5/ac112889bfc68e6cf4eda1e4325789b166c51c6cd29d5633e28fb2c2f966/multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"}, + {url = "https://files.pythonhosted.org/packages/57/23/3955d3bba16dc6d75b1993d52a1b32dc93c795920e213862fab220c7d030/multidict-6.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161"}, + {url = "https://files.pythonhosted.org/packages/59/28/e50cc24c56609d11f7232606f73981620e94e3445791d9501e21c4c73a61/multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"}, + {url = "https://files.pythonhosted.org/packages/5c/4d/976b2e5fadc2b6e5e6208fb1566669460adde3f41d7622db3afa90fb2dbf/multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"}, + {url = "https://files.pythonhosted.org/packages/5d/a0/33b0b030148e9e0882d8b9f2404b8b3cc5e4718041fe6856602ccad81fa9/multidict-6.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706"}, + {url = "https://files.pythonhosted.org/packages/5f/eb/2023167c9533d62e2afcba7acb0dc98420bcf9fc27eff5a83c2bbd013b65/multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"}, + {url = "https://files.pythonhosted.org/packages/65/c4/fcbe7b0749a20d0b9adfaec89a46ceb16a187f944230fb30f62c64e6a25e/multidict-6.0.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067"}, + {url = "https://files.pythonhosted.org/packages/69/48/2750fd3ace4d778b4e1f7110db3ad637906de3496abc9c450ce726b97337/multidict-6.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1"}, + {url = "https://files.pythonhosted.org/packages/6d/9c/e5515fd09f0811045946872baeb08eb61993115d195eb8900083da21f17c/multidict-6.0.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603"}, + {url = "https://files.pythonhosted.org/packages/84/2b/2503ef1243e598d54d1516a3780858a70e9ec5de57cf03888010ee906976/multidict-6.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2"}, + {url = "https://files.pythonhosted.org/packages/85/0c/8413a4a0ad4eb4f7987546b9cd84717a14c3639efec6bc1f2f3d1d9de98d/multidict-6.0.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60"}, + {url = "https://files.pythonhosted.org/packages/89/70/6cb6d8e81d269fff05624bbd2db0a98351ed2f655c39a8f8761c362b4755/multidict-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775"}, + {url = "https://files.pythonhosted.org/packages/8f/f9/e14b11f78b937d2a5982593d5a238058679bd120979b2c5b94ea8ba125fc/multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"}, + {url = "https://files.pythonhosted.org/packages/94/e7/a1484aa7d711bc346a37dfa2f23895cc568f9f5a5f9e86498864d2e17b7e/multidict-6.0.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a"}, + {url = "https://files.pythonhosted.org/packages/96/9a/96830785d7eb3c72782fda15572ea9ed31fd67a071eab0ffad6859458e4d/multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"}, + {url = "https://files.pythonhosted.org/packages/9d/5a/34bd606569178ad8a931ea4d59cda926b046cfa4c01b0191c2e04cfd44c2/multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"}, + {url = "https://files.pythonhosted.org/packages/9f/d2/49cf9fa8a79e5aa9df5139b54842bb45b6a4cacdefc2defcc1aa10e8b1ea/multidict-6.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca"}, + {url = "https://files.pythonhosted.org/packages/a7/72/fe07bee3dd045d041f5c2e542ceaf9b685ee4775c38702e6584faacd64fd/multidict-6.0.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2"}, + {url = "https://files.pythonhosted.org/packages/ac/2d/0fa5bf39a8a595ded860adacc4188749013775c16a78472954f49baa61fa/multidict-6.0.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d"}, + {url = "https://files.pythonhosted.org/packages/b0/6d/03a5b702a0ad4d3aa4cf101acd8758ff8438fef0311bf90e7c72a80152ef/multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"}, + {url = "https://files.pythonhosted.org/packages/b0/a2/5eb04a471c99b1cc9f3c4f6aa17cbbbedf60c89f4d949ddcc4251b4ae5ab/multidict-6.0.4-cp38-cp38-win32.whl", hash = "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779"}, + {url = "https://files.pythonhosted.org/packages/b4/7a/3f0b0e533fd1b73662723cb45869f4d32df643458d78c2fa7b946be98494/multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"}, + {url = "https://files.pythonhosted.org/packages/b7/c4/24a83a598d3622be56679d233c9fa19e0334a8047b72dcfc1e1296426ca9/multidict-6.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660"}, + {url = "https://files.pythonhosted.org/packages/bb/e4/ea5687129b0cb781aba596bd08abb2aca3c8051e41aabf989c966e93af04/multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"}, + {url = "https://files.pythonhosted.org/packages/bb/ec/ea3435f339cfad0d0a5e9e533a362d230325029deea9cdba6730fcfc1e00/multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"}, + {url = "https://files.pythonhosted.org/packages/bd/50/7beeed47a950011ea0abf50541fecd67d6880719ac0e797b3dc214d6f102/multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"}, + {url = "https://files.pythonhosted.org/packages/bf/e8/9e732d21adc5321bf3adcde8e712a8af20f5cf32beaaf08267ee0dad47ca/multidict-6.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1"}, + {url = "https://files.pythonhosted.org/packages/c3/c5/b583cd706f88ef57811229b67d6c4c0fcda56bee49a913156dd401aaa729/multidict-6.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c"}, + {url = "https://files.pythonhosted.org/packages/c6/4d/7fa88fdd8f4491381ad2b2ba6e6f725823aa52e73f4541330374b26094ef/multidict-6.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d"}, + {url = "https://files.pythonhosted.org/packages/d0/21/d737fe1cac90fb89b0959194d12747024ea95d52032daef9d2ac3cf18ce0/multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"}, + {url = "https://files.pythonhosted.org/packages/d2/cf/d00992d281fb953a01685d9b2e68f66901c7dee7bcb75dad1a5ef9a879d3/multidict-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480"}, + {url = "https://files.pythonhosted.org/packages/d5/14/cb152ce2ec0874a4f6842938cd34e8e84195331d1108129b8630012b3176/multidict-6.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde"}, + {url = "https://files.pythonhosted.org/packages/d5/eb/22de4f5935f4d754b0f53d323643a1b4b7fa796e02bf3a0df7dec150269f/multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"}, + {url = "https://files.pythonhosted.org/packages/d8/a5/4ee9ed42f0eadf10a7eaa0d67e26107c0385e62cee49bacd2bd7ad934ae9/multidict-6.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78"}, + {url = "https://files.pythonhosted.org/packages/da/fe/49febc2d6b6a9bf072a4801a8ba2b5a4795f7b5444659d834c598b6b0ee1/multidict-6.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe"}, + {url = "https://files.pythonhosted.org/packages/db/7e/f007ec4ea4d6626aa4e659ae3631345cb928ff07445577914884c3355277/multidict-6.0.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176"}, + {url = "https://files.pythonhosted.org/packages/dd/bd/cce218536b377efbee70d8680a97fd282f4e1e9f7ebff95c4ea28deef87a/multidict-6.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60"}, + {url = "https://files.pythonhosted.org/packages/de/c2/2b6be6b6194064efe429b77994f3153ffd18a92b0b6422de3c702b58b150/multidict-6.0.4-cp39-cp39-win32.whl", hash = "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313"}, + {url = "https://files.pythonhosted.org/packages/e1/2b/e2b9ff85a5f973c7636d07e58ede554262a75b435eb6fe53e67ec4749953/multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"}, + {url = "https://files.pythonhosted.org/packages/e4/18/79a66879c57c37a2a721ca1aea18953f0f291ea8a8e7334fe5091a4c3111/multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"}, + {url = "https://files.pythonhosted.org/packages/e4/41/ade43649e3c35178a81827eb960a7480842fe36c51d4a16a2a68e396e0d6/multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"}, + {url = "https://files.pythonhosted.org/packages/e5/75/b629e322641d884f438fd7ca959d69dae94b25bc59035a97dd48d931515b/multidict-6.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951"}, + {url = "https://files.pythonhosted.org/packages/eb/97/05b51bd39ba10ad7ae6530ae05e050a1cac91d42dcafd40c40d388e057b4/multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"}, + {url = "https://files.pythonhosted.org/packages/f0/c1/de389de822e8442717e7fda86496a47af8a132104e1601f3419d26dff334/multidict-6.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35"}, + {url = "https://files.pythonhosted.org/packages/f1/d2/d735d40355ce41f6d1c50a5d4feef47cd4aad0e2809dd2c8cb01601f04ac/multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"}, + {url = "https://files.pythonhosted.org/packages/f1/d7/7f26fe2e790654dcc82283c17b69534c7f30213b63628e7420391d609166/multidict-6.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e"}, + {url = "https://files.pythonhosted.org/packages/f5/cf/416f84a8c7954c571881b01c839312ec81e222b3986c8baedc57f476cc1b/multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"}, + {url = "https://files.pythonhosted.org/packages/fc/54/8e025ae4e31d899e4528a570941eb7048512392b454acccf69c2dccfcb0d/multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"}, + {url = "https://files.pythonhosted.org/packages/fc/5b/0a4205a1248fb152f596a03c971c6ef1585d0c98e56b6886dc35d084e366/multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"}, + {url = "https://files.pythonhosted.org/packages/fe/0c/8469202f8f4b0e65816f91c3febc4bda7316c995b59ecdf3b15c574f7a24/multidict-6.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258"}, +] +"nodeenv 1.8.0" = [ + {url = "https://files.pythonhosted.org/packages/1a/e6/6d2ead760a9ddb35e65740fd5a57e46aadd7b0c49861ab24f94812797a1c/nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, + {url = "https://files.pythonhosted.org/packages/48/92/8e83a37d3f4e73c157f9fcf9fb98ca39bd94701a469dc093b34dca31df65/nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, +] +"opentelemetry-api 1.19.0" = [ + {url = "https://files.pythonhosted.org/packages/11/2e/a6a5b453957cf72050ee7f510597fa9377d5b14577c89d9290aa103fc9db/opentelemetry_api-1.19.0-py3-none-any.whl", hash = "sha256:dcd2a0ad34b691964947e1d50f9e8c415c32827a1d87f0459a72deb9afdf5597"}, + {url = "https://files.pythonhosted.org/packages/7e/35/d9fa13c9ac63ce67a5b5a84e754f32fe0ce3be48242cf5b76554cdcb7e90/opentelemetry_api-1.19.0.tar.gz", hash = "sha256:db374fb5bea00f3c7aa290f5d94cea50b659e6ea9343384c5f6c2bb5d5e8db65"}, +] +"opentelemetry-instrumentation 0.38b0" = [ + {url = "https://files.pythonhosted.org/packages/01/aa/edc30bd716369543ef3fc2f08fa603b1c15ca6b87bed936a0ad1bc2d34e4/opentelemetry_instrumentation-0.38b0.tar.gz", hash = "sha256:3dbe93248eec7652d5725d3c6d2f9dd048bb8fda6b0505aadbc99e51638d833c"}, + {url = "https://files.pythonhosted.org/packages/b0/10/1948d4056b17a214e77f62b225d768916fd28ffc523673b3988259dd11d5/opentelemetry_instrumentation-0.38b0-py3-none-any.whl", hash = "sha256:48eed87e5db9d2cddd57a8ea359bd15318560c0ffdd80d90a5fc65816e15b7f4"}, +] +"opentelemetry-instrumentation-asgi 0.38b0" = [ + {url = "https://files.pythonhosted.org/packages/1a/4b/8038bcb42aba6b6c71d54026ad9b29fdc1bb7ee6d198571dcebdb1893d44/opentelemetry_instrumentation_asgi-0.38b0-py3-none-any.whl", hash = "sha256:c5bba11505008a3cd1b2c42b72f85f3f4f5af50ab931eddd0b01bde376dc5971"}, + {url = "https://files.pythonhosted.org/packages/27/83/d0779230c4d45621231a24c0e76ce91842c191e87ac5130c1f552a5a3bff/opentelemetry_instrumentation_asgi-0.38b0.tar.gz", hash = "sha256:32d1034c253de6048d0d0166b304f9125267ca9329e374202ebe011a206eba53"}, +] +"opentelemetry-instrumentation-fastapi 0.38b0" = [ + {url = "https://files.pythonhosted.org/packages/0e/8c/6b5f411ce2d8b64b0aaf8bf288fa523f10a325ed1efbadc5f1e14774edf7/opentelemetry_instrumentation_fastapi-0.38b0-py3-none-any.whl", hash = "sha256:91139586732e437b1c3d5cf838dc5be910bce27b4b679612112be03fcc4fa2aa"}, + {url = "https://files.pythonhosted.org/packages/28/60/d1769efb924d9f5bbad23eaea9ef7c2f4985c01cdcdeeb42156ddbfe19c6/opentelemetry_instrumentation_fastapi-0.38b0.tar.gz", hash = "sha256:8946fd414084b305ad67556a1907e2d4a497924d023effc5ea3b4b1b0c55b256"}, +] +"opentelemetry-semantic-conventions 0.38b0" = [ + {url = "https://files.pythonhosted.org/packages/0b/b0/c8bdd02ce280f41e9d0568a37ead4c0153d5200f887a5f6858839daa5bb6/opentelemetry_semantic_conventions-0.38b0-py3-none-any.whl", hash = "sha256:b0ba36e8b70bfaab16ee5a553d809309cc11ff58aec3d2550d451e79d45243a7"}, + {url = "https://files.pythonhosted.org/packages/cb/67/2e34aa3dde0558017ec071591d04aeb99f29dadad9f72ff59e490011ccb6/opentelemetry_semantic_conventions-0.38b0.tar.gz", hash = "sha256:37f09e47dd5fc316658bf9ee9f37f9389b21e708faffa4a65d6a3de484d22309"}, +] +"opentelemetry-util-http 0.38b0" = [ + {url = "https://files.pythonhosted.org/packages/ce/3f/d2dd64948895f1a32c8e57e9c433079d7d6fa9bb94b93a90e38ca46053a1/opentelemetry_util_http-0.38b0.tar.gz", hash = "sha256:85eb032b6129c4d7620583acf574e99fe2e73c33d60e256b54af436f76ceb5ae"}, + {url = "https://files.pythonhosted.org/packages/f0/a3/54ea1d852bbc5348743017b5ef09f9119880bda147021d3d5e651d51cc04/opentelemetry_util_http-0.38b0-py3-none-any.whl", hash = "sha256:8e5f0451eeb5307b2c628dd799886adc5e113fb13a7207c29c672e8d168eabd8"}, +] +"packaging 23.1" = [ + {url = "https://files.pythonhosted.org/packages/ab/c3/57f0601a2d4fe15de7a553c00adbc901425661bf048f2a22dfc500caf121/packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, + {url = "https://files.pythonhosted.org/packages/b9/6c/7c6658d258d7971c5eb0d9b69fa9265879ec9a9158031206d47800ae2213/packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, +] +"passlib 1.7.4" = [ + {url = "https://files.pythonhosted.org/packages/3b/a4/ab6b7589382ca3df236e03faa71deac88cae040af60c071a78d254a62172/passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"}, + {url = "https://files.pythonhosted.org/packages/b6/06/9da9ee59a67fae7761aab3ccc84fa4f3f33f125b370f1ccdb915bf967c11/passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04"}, +] +"platformdirs 3.10.0" = [ + {url = "https://files.pythonhosted.org/packages/14/51/fe5a0d6ea589f0d4a1b97824fb518962ad48b27cd346dcdfa2405187997a/platformdirs-3.10.0-py3-none-any.whl", hash = "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d"}, + {url = "https://files.pythonhosted.org/packages/dc/99/c922839819f5d00d78b3a1057b5ceee3123c69b2216e776ddcb5a4c265ff/platformdirs-3.10.0.tar.gz", hash = "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d"}, +] +"pluggy 1.2.0" = [ + {url = "https://files.pythonhosted.org/packages/51/32/4a79112b8b87b21450b066e102d6608907f4c885ed7b04c3fdb085d4d6ae/pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, + {url = "https://files.pythonhosted.org/packages/8a/42/8f2833655a29c4e9cb52ee8a2be04ceac61bcff4a680fb338cbd3d1e322d/pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, +] +"pre-commit 3.3.3" = [ + {url = "https://files.pythonhosted.org/packages/35/0e/564c71fe3cdf59a4acaaccaea354d066e5d9044eba564dac070bb2075432/pre_commit-3.3.3.tar.gz", hash = "sha256:a2256f489cd913d575c145132ae196fe335da32d91a8294b7afe6622335dd023"}, + {url = "https://files.pythonhosted.org/packages/e3/b7/1d145c985d8be9729672a45b8b8113030ad60dff45dec592efc4e5f5897a/pre_commit-3.3.3-py2.py3-none-any.whl", hash = "sha256:10badb65d6a38caff29703362271d7dca483d01da88f9d7e05d0b97171c136cb"}, +] +"prompt-toolkit 3.0.39" = [ + {url = "https://files.pythonhosted.org/packages/9a/02/76cadde6135986dc1e82e2928f35ebeb5a1af805e2527fe466285593a2ba/prompt_toolkit-3.0.39.tar.gz", hash = "sha256:04505ade687dc26dc4284b1ad19a83be2f2afe83e7a828ace0c72f3a1df72aac"}, + {url = "https://files.pythonhosted.org/packages/a9/b4/ba77c84edf499877317225d7b7bc047a81f7c2eed9628eeb6bab0ac2e6c9/prompt_toolkit-3.0.39-py3-none-any.whl", hash = "sha256:9dffbe1d8acf91e3de75f3b544e4842382fc06c6babe903ac9acb74dc6e08d88"}, +] +"pyasn1 0.5.0" = [ + {url = "https://files.pythonhosted.org/packages/14/e5/b56a725cbde139aa960c26a1a3ca4d4af437282e20b5314ee6a3501e7dfc/pyasn1-0.5.0-py2.py3-none-any.whl", hash = "sha256:87a2121042a1ac9358cabcaf1d07680ff97ee6404333bacca15f76aa8ad01a57"}, + {url = "https://files.pythonhosted.org/packages/61/ef/945a8bcda7895717c8ba4688c08a11ef6454f32b8e5cb6e352a9004ee89d/pyasn1-0.5.0.tar.gz", hash = "sha256:97b7290ca68e62a832558ec3976f15cbf911bf5d7c7039d8b861c2a0ece69fde"}, +] +"pycparser 2.21" = [ + {url = "https://files.pythonhosted.org/packages/5e/0b/95d387f5f4433cb0f53ff7ad859bd2c6051051cebbb564f139a999ab46de/pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, + {url = "https://files.pythonhosted.org/packages/62/d5/5f610ebe421e85889f2e55e33b7f9a6795bd982198517d912eb1c76e1a53/pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, +] +"pydantic 1.10.12" = [ + {url = "https://files.pythonhosted.org/packages/02/bd/2c6a34eeffcc437fca7d5c4b6cf7745fcc806842de5fced482d4cdba55f0/pydantic-1.10.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d5a58feb9a39f481eda4d5ca220aa8b9d4f21a41274760b9bc66bfd72595b86"}, + {url = "https://files.pythonhosted.org/packages/06/46/1da67901c12513c34bf853ad6a89abe991496bc1171f72c41fb78a15238f/pydantic-1.10.12-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6a9dfa722316f4acf4460afdf5d41d5246a80e249c7ff475c43a3a1e9d75cf62"}, + {url = "https://files.pythonhosted.org/packages/09/2a/111c5fcd8871c79a46932a1f28f7149fbbe4da56b699d1504ffd837ad26a/pydantic-1.10.12-cp37-cp37m-win_amd64.whl", hash = "sha256:e0a16d274b588767602b7646fa05af2782576a6cf1022f4ba74cbb4db66f6ca8"}, + {url = "https://files.pythonhosted.org/packages/0e/88/8e503b95402c73e023d705293ff0659f133976cdd726f7f8dffce5e8eef4/pydantic-1.10.12-cp310-cp310-win_amd64.whl", hash = "sha256:e4129b528c6baa99a429f97ce733fff478ec955513630e61b49804b6cf9b224a"}, + {url = "https://files.pythonhosted.org/packages/0e/94/170330a1338cea22602237d4db8632b6f5a2a35e85f1e0434601186123b7/pydantic-1.10.12-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab26038b8375581dc832a63c948f261ae0aa21f1d34c1293469f135fa92972a5"}, + {url = "https://files.pythonhosted.org/packages/15/ef/24f51eee3ccb81d42aeee387d4cf43a5d0e8ddafad967bce7754d44b755d/pydantic-1.10.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b0d191db0f92dfcb1dec210ca244fdae5cbe918c6050b342d619c09d31eea0cc"}, + {url = "https://files.pythonhosted.org/packages/19/ec/b55e9da17416264cf1a12ca7354b9491c4c55db44c63edac98afb625cfb3/pydantic-1.10.12-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a73f489aebd0c2121ed974054cb2759af8a9f747de120acd2c3394cf84176ccb"}, + {url = "https://files.pythonhosted.org/packages/21/8e/1b026f908ca93b7efeed664666e5bddfc8adea79f8877051897e9e3a90d7/pydantic-1.10.12-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dea7adcc33d5d105896401a1f37d56b47d443a2b2605ff8a969a0ed5543f7e33"}, + {url = "https://files.pythonhosted.org/packages/23/89/8eeaf92abad9e2c966fbf1c85e5492bac5f56d205189229fd33a22060f0f/pydantic-1.10.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5f805d2d5d0a41633651a73fa4ecdd0b3d7a49de4ec3fadf062fe16501ddbf1"}, + {url = "https://files.pythonhosted.org/packages/24/5c/7d600cf0c0c4fa577c33bafd4a7ee567bb19552dbd093b0d4c756ea6cc13/pydantic-1.10.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c076be61cd0177a8433c0adcb03475baf4ee91edf5a4e550161ad57fc90f523"}, + {url = "https://files.pythonhosted.org/packages/34/cb/8ade225a018c9449cb16b4839f825f1bace15fefff23583345ca31fa42ff/pydantic-1.10.12-cp39-cp39-win_amd64.whl", hash = "sha256:fdbdd1d630195689f325c9ef1a12900524dceb503b00a987663ff4f58669b93d"}, + {url = "https://files.pythonhosted.org/packages/3b/9b/a7631bf35e55326fd74654fe6bd896478f47d65e97ca69e60ddb1b3823ee/pydantic-1.10.12.tar.gz", hash = "sha256:0fe8a415cea8f340e7a9af9c54fc71a649b43e8ca3cc732986116b3cb135d303"}, + {url = "https://files.pythonhosted.org/packages/4d/96/c381c5fc6eaaec252d3acb89c6babc5f500e71feb6c5b612f6fe602bc0a2/pydantic-1.10.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b7ccf02d7eb340b216ec33e53a3a629856afe1c6e0ef91d84a4e6f2fb2ca70fe"}, + {url = "https://files.pythonhosted.org/packages/51/69/d197a166846f5f4b72d133f18daed1f5d4290abeeb5bfac16bebf20158a8/pydantic-1.10.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a5e7add47a5b5a40c49b3036d464e3c7802f8ae0d1e66035ea16aa5b7a3923ed"}, + {url = "https://files.pythonhosted.org/packages/55/e5/55916738277c518d3cbf3562fbd2e85109a834e44e8a92343a95af4e42e9/pydantic-1.10.12-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2f9a6fab5f82ada41d56b0602606a5506aab165ca54e52bc4545028382ef1c5d"}, + {url = "https://files.pythonhosted.org/packages/58/26/ca79779dc217222d308254b4d4312108c4ac334fb63d97596e0ba0982868/pydantic-1.10.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a1fcb59f2f355ec350073af41d927bf83a63b50e640f4dbaa01053a28b7a7718"}, + {url = "https://files.pythonhosted.org/packages/5d/68/7a0c5f8b854d3fad9cd82a6312205025597481e46b4ec36f6dea4f1fb93b/pydantic-1.10.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b30bcb8cbfccfcf02acb8f1a261143fab622831d9c0989707e0e659f77a18e0"}, + {url = "https://files.pythonhosted.org/packages/78/03/5b35c8fb792c3ea0408ac82257952b11ca2507ca46c21826b180514293b3/pydantic-1.10.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ef6c96b2baa2100ec91a4b428f80d8f28a3c9e53568219b6c298c1125572ebc6"}, + {url = "https://files.pythonhosted.org/packages/7c/e0/2c957d4365c1ee5f9494697bc7c8655b39b5d4fc49619b17775680a3d86c/pydantic-1.10.12-py3-none-any.whl", hash = "sha256:b749a43aa51e32839c9d71dc67eb1e4221bb04af1033a32e3923d46f9effa942"}, + {url = "https://files.pythonhosted.org/packages/91/cd/0b3e708a700c2561cf632aab1bfc1fd79aeafb788543f07132af54a173ee/pydantic-1.10.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:771735dc43cf8383959dc9b90aa281f0b6092321ca98677c5fb6125a6f56d58d"}, + {url = "https://files.pythonhosted.org/packages/92/a7/8186274cc8903a1c88c7aeb923b89318df08ce9d500dc0b36e1e63bb5b7a/pydantic-1.10.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ca48477862372ac3770969b9d75f1bf66131d386dba79506c46d75e6b48c1e09"}, + {url = "https://files.pythonhosted.org/packages/9d/fb/103bb75d776a887927fb2252f58c6a6a63a90d3d7c07c611b84507658dbb/pydantic-1.10.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1289c180abd4bd4555bb927c42ee42abc3aee02b0fb2d1223fb7c6e5bef87dbe"}, + {url = "https://files.pythonhosted.org/packages/ae/68/6e7218d1e2983abab8be0659ebc13a15da19234c3cfb22a6b0cffaf568e7/pydantic-1.10.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5d1197e462e0364906cbc19681605cb7c036f2475c899b6f296104ad42b9f5fb"}, + {url = "https://files.pythonhosted.org/packages/af/09/09c74d2c627fda8dade91392f43a014de872519246e5682f12c89a6a3596/pydantic-1.10.12-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:598da88dfa127b666852bef6d0d796573a8cf5009ffd62104094a4fe39599565"}, + {url = "https://files.pythonhosted.org/packages/b8/ae/8c3ffe3dafeaeb0fa08d6622e7fb074c7f42921fe8916c8de6f47662f9a4/pydantic-1.10.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69328e15cfda2c392da4e713443c7dbffa1505bc9d566e71e55abe14c97ddc62"}, + {url = "https://files.pythonhosted.org/packages/bc/e0/0371e9b6c910afe502e5fe18cc94562bfd9399617c7b4f5b6e13c29115b3/pydantic-1.10.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fb2aa3ab3728d950bcc885a2e9eff6c8fc40bc0b7bb434e555c215491bcf48b"}, + {url = "https://files.pythonhosted.org/packages/c6/e9/6ffcfe4d569e25cc49da377ffc81b0041c2816aa3af688a9e0c4e2de8f02/pydantic-1.10.12-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:549a8e3d81df0a85226963611950b12d2d334f214436a19537b2efed61b7639a"}, + {url = "https://files.pythonhosted.org/packages/c9/7e/5e3c7982421495864e26b043262fe0b13bb493e86cb9772c62fd1099d6bf/pydantic-1.10.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2031de0967c279df0d8a1c72b4ffc411ecd06bac607a212892757db7462fc494"}, + {url = "https://files.pythonhosted.org/packages/cb/bd/4baeea241ee018a0d4ef52d112ed1a9c7b9f96d1a2cec40f6ad82c66eca0/pydantic-1.10.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2a7bac939fa326db1ab741c9d7f44c565a1d1e80908b3797f7f81a4f86bc8d33"}, + {url = "https://files.pythonhosted.org/packages/d0/e3/de7a8ccdd22a0285fb0bcadc7dff30b30ba745e4771597346c21bf4b28a0/pydantic-1.10.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ba5b2e6fe6ca2b7e013398bc7d7b170e21cce322d266ffcd57cca313e54fb246"}, + {url = "https://files.pythonhosted.org/packages/dc/8e/fcb4912bedb35203323a2b0718ce1a160615ea4a0fd71e98cc2c50808cf9/pydantic-1.10.12-cp38-cp38-win_amd64.whl", hash = "sha256:1eb2085c13bce1612da8537b2d90f549c8cbb05c67e8f22854e201bde5d98a47"}, + {url = "https://files.pythonhosted.org/packages/e1/6b/6a68bd4f0837accdb78cf07e8590f2c9f00334003681b94d4957424248f2/pydantic-1.10.12-cp311-cp311-win_amd64.whl", hash = "sha256:87afda5539d5140cb8ba9e8b8c8865cb5b1463924d38490d73d3ccfd80896b3f"}, + {url = "https://files.pythonhosted.org/packages/e5/79/40b770ec6be5b1591e3767d1006bd9ee2aec9c163be4917f1a485128e07e/pydantic-1.10.12-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fcfb5296d7877af406ba1547dfde9943b1256d8928732267e2653c26938cd9c"}, + {url = "https://files.pythonhosted.org/packages/f5/6a/0a999a133c43f8a410566164d8c19c05a674d852405d7f1feb84a314a82e/pydantic-1.10.12-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba5c4a8552bff16c61882db58544116d021d0b31ee7c66958d14cf386a5b5350"}, + {url = "https://files.pythonhosted.org/packages/f9/e7/8dc6b418c3eefd15274e93e58ebd7ed95a650cc0ee3f38673bb56a8aa3f7/pydantic-1.10.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:795e34e6cc065f8f498c89b894a3c6da294a936ee71e644e4bd44de048af1405"}, + {url = "https://files.pythonhosted.org/packages/ff/c2/731ebec42c8c649f3c0a70724c1935169cb83afe0c7c22b7c05bb00c3e9e/pydantic-1.10.12-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c79e6a11a07da7374f46970410b41d5e266f7f38f6a17a9c4823db80dadf4303"}, +] +"pygments 2.16.1" = [ + {url = "https://files.pythonhosted.org/packages/43/88/29adf0b44ba6ac85045e63734ae0997d3c58d8b1a91c914d240828d0d73d/Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, + {url = "https://files.pythonhosted.org/packages/d6/f7/4d461ddf9c2bcd6a4d7b2b139267ca32a69439387cc1f02a924ff8883825/Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, +] +"pylint 2.17.5" = [ + {url = "https://files.pythonhosted.org/packages/19/07/24a57a4b12b8ea09f61436db7b312676aa2e526a34dbd0f2866e3e563e1a/pylint-2.17.5.tar.gz", hash = "sha256:f7b601cbc06fef7e62a754e2b41294c2aa31f1cb659624b9a85bcba29eaf8252"}, + {url = "https://files.pythonhosted.org/packages/63/cc/00cbe3f09bd6d98d79ee66cf76451d253fb1a8a59029535ea2b6ba8a824d/pylint-2.17.5-py3-none-any.whl", hash = "sha256:73995fb8216d3bed149c8d51bba25b2c52a8251a2c8ac846ec668ce38fab5413"}, +] +"pytest 7.4.0" = [ + {url = "https://files.pythonhosted.org/packages/33/b2/741130cbcf2bbfa852ed95a60dc311c9e232c7ed25bac3d9b8880a8df4ae/pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, + {url = "https://files.pythonhosted.org/packages/a7/f3/dadfbdbf6b6c8b5bd02adb1e08bc9fbb45ba51c68b0893fa536378cdf485/pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, +] +"pytest-asyncio 0.21.1" = [ + {url = "https://files.pythonhosted.org/packages/5a/85/d39ef5f69d5597a206f213ce387bcdfa47922423875829f7a98a87d33281/pytest-asyncio-0.21.1.tar.gz", hash = "sha256:40a7eae6dded22c7b604986855ea48400ab15b069ae38116e8c01238e9eeb64d"}, + {url = "https://files.pythonhosted.org/packages/7d/2c/2e5ab8708667972ee31b88bb6fed680ed5ba92dfc2db28e07d0d68d8b3b1/pytest_asyncio-0.21.1-py3-none-any.whl", hash = "sha256:8666c1c8ac02631d7c51ba282e0c69a8a452b211ffedf2599099845da5c5c37b"}, +] +"pytest-cov 4.1.0" = [ + {url = "https://files.pythonhosted.org/packages/7a/15/da3df99fd551507694a9b01f512a2f6cf1254f33601605843c3775f39460/pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, + {url = "https://files.pythonhosted.org/packages/a7/4b/8b78d126e275efa2379b1c2e09dc52cf70df16fc3b90613ef82531499d73/pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, +] +"pytest-integration 0.2.3" = [ + {url = "https://files.pythonhosted.org/packages/1b/41/9b393be6252635e4d39c3e62805018c42bfcc486b42246b582b755ff9ad3/pytest_integration-0.2.3-py3-none-any.whl", hash = "sha256:7f59ed1fa1cc8cb240f9495b68bc02c0421cce48589f78e49b7b842231604b12"}, + {url = "https://files.pythonhosted.org/packages/35/e0/c823048dc0866f2e0fa2e4a34cd6ec290697b238b7672b30cb07c65e59cc/pytest_integration-0.2.3.tar.gz", hash = "sha256:b00988a5de8a6826af82d4c7a3485b43fbf32c11235e9f4a8b7225eef5fbcf65"}, +] +"pytest-mock 3.11.1" = [ + {url = "https://files.pythonhosted.org/packages/d8/2d/b3a811ec4fa24190a9ec5013e23c89421a7916167c6240c31fdc445f850c/pytest-mock-3.11.1.tar.gz", hash = "sha256:7f6b125602ac6d743e523ae0bfa71e1a697a2f5534064528c6ff84c2f7c2fc7f"}, + {url = "https://files.pythonhosted.org/packages/da/85/80ae98e019a429445bfb74e153d4cb47c3695e3e908515e95e95c18237e5/pytest_mock-3.11.1-py3-none-any.whl", hash = "sha256:21c279fff83d70763b05f8874cc9cfb3fcacd6d354247a976f9529d19f9acf39"}, +] +"pytest-xdist 3.3.1" = [ + {url = "https://files.pythonhosted.org/packages/db/d1/70a67f79b31cb5cba09c96bc4590c6ac22608558664901df03fdee24f6a6/pytest_xdist-3.3.1-py3-none-any.whl", hash = "sha256:ff9daa7793569e6a68544850fd3927cd257cc03a7ef76c95e86915355e82b5f2"}, + {url = "https://files.pythonhosted.org/packages/e2/5c/eae1b20cbea054d4e11ca5cb4f9b163000e885a2ae62e433375e8cdf1097/pytest-xdist-3.3.1.tar.gz", hash = "sha256:d5ee0520eb1b7bcca50a60a518ab7a7707992812c578198f8b44fdfac78e8c93"}, +] +"python-dateutil 2.8.2" = [ + {url = "https://files.pythonhosted.org/packages/36/7a/87837f39d0296e723bb9b62bbb257d0355c7f6128853c78955f57342a56d/python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, + {url = "https://files.pythonhosted.org/packages/4c/c4/13b4776ea2d76c115c1d1b84579f3764ee6d57204f6be27119f13a61d0a9/python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, +] +"python-dotenv 0.21.1" = [ + {url = "https://files.pythonhosted.org/packages/64/62/f19d1e9023aacb47241de3ab5a5d5fedf32c78a71a9e365bb2153378c141/python_dotenv-0.21.1-py3-none-any.whl", hash = "sha256:41e12e0318bebc859fcc4d97d4db8d20ad21721a6aa5047dd59f090391cb549a"}, + {url = "https://files.pythonhosted.org/packages/f5/d7/d548e0d5a68b328a8d69af833a861be415a17cb15ce3d8f0cd850073d2e1/python-dotenv-0.21.1.tar.gz", hash = "sha256:1c93de8f636cde3ce377292818d0e440b6e45a82f215c3744979151fa8151c49"}, +] +"python-jose 3.3.0" = [ + {url = "https://files.pythonhosted.org/packages/bd/2d/e94b2f7bab6773c70efc70a61d66e312e1febccd9e0db6b9e0adf58cbad1/python_jose-3.3.0-py2.py3-none-any.whl", hash = "sha256:9b1376b023f8b298536eedd47ae1089bcdb848f1535ab30555cd92002d78923a"}, + {url = "https://files.pythonhosted.org/packages/e4/19/b2c86504116dc5f0635d29f802da858404d77d930a25633d2e86a64a35b3/python-jose-3.3.0.tar.gz", hash = "sha256:55779b5e6ad599c6336191246e95eb2293a9ddebd555f796a65f838f07e5d78a"}, +] +"python-multipart 0.0.6" = [ + {url = "https://files.pythonhosted.org/packages/2d/23/abcfad10c3348cb6358400f8adbc21b523bbc6c954494fd0974428068672/python_multipart-0.0.6.tar.gz", hash = "sha256:e9925a80bb668529f1b67c7fdb0a5dacdd7cbfc6fb0bff3ea443fe22bdd62132"}, + {url = "https://files.pythonhosted.org/packages/b4/ff/b1e11d8bffb5e0e1b6d27f402eeedbeb9be6df2cdbc09356a1ae49806dbf/python_multipart-0.0.6-py3-none-any.whl", hash = "sha256:ee698bab5ef148b0a760751c261902cd096e57e10558e11aca17646b74ee1c18"}, +] +"pyyaml 6.0.1" = [ + {url = "https://files.pythonhosted.org/packages/02/74/b2320ebe006b6a521cf929c78f12a220b9db319b38165023623ed195654b/PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {url = "https://files.pythonhosted.org/packages/03/f7/4f8b71f3ce8cfb2c06e814aeda5b26ecc62ecb5cf85f5c8898be34e6eb6a/PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {url = "https://files.pythonhosted.org/packages/06/92/e0224aa6ebf9dc54a06a4609da37da40bb08d126f5535d81bff6b417b2ae/PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {url = "https://files.pythonhosted.org/packages/0e/88/21b2f16cb2123c1e9375f2c93486e35fdc86e63f02e274f0e99c589ef153/PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {url = "https://files.pythonhosted.org/packages/1e/ae/964ccb88a938f20ece5754878f182cfbd846924930d02d29d06af8d4c69e/PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {url = "https://files.pythonhosted.org/packages/24/62/7fcc372442ec8ea331da18c24b13710e010c5073ab851ef36bf9dacb283f/PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {url = "https://files.pythonhosted.org/packages/24/97/9b59b43431f98d01806b288532da38099cc6f2fea0f3d712e21e269c0279/PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {url = "https://files.pythonhosted.org/packages/27/d5/fb4f7a3c96af89c214387af42c76117d2c2a0a40576e217632548a6e1aff/PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {url = "https://files.pythonhosted.org/packages/28/09/55f715ddbf95a054b764b547f617e22f1d5e45d83905660e9a088078fe67/PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {url = "https://files.pythonhosted.org/packages/29/0f/9782fa5b10152abf033aec56a601177ead85ee03b57781f2d9fced09eefc/PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {url = "https://files.pythonhosted.org/packages/29/61/bf33c6c85c55bc45a29eee3195848ff2d518d84735eb0e2d8cb42e0d285e/PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {url = "https://files.pythonhosted.org/packages/41/9a/1c4c51f1a0d2b6fd805973701ab0ec84d5e622c5aaa573b0e1157f132809/PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {url = "https://files.pythonhosted.org/packages/4a/4b/c71ef18ef83c82f99e6da8332910692af78ea32bd1d1d76c9787dfa36aea/PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {url = "https://files.pythonhosted.org/packages/4d/f1/08f06159739254c8947899c9fc901241614195db15ba8802ff142237664c/PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {url = "https://files.pythonhosted.org/packages/57/c5/5d09b66b41d549914802f482a2118d925d876dc2a35b2d127694c1345c34/PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {url = "https://files.pythonhosted.org/packages/5b/07/10033a403b23405a8fc48975444463d3d10a5c2736b7eb2550b07b367429/PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {url = "https://files.pythonhosted.org/packages/5e/94/7d5ee059dfb92ca9e62f4057dcdec9ac08a9e42679644854dc01177f8145/PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {url = "https://files.pythonhosted.org/packages/62/2a/df7727c52e151f9e7b852d7d1580c37bd9e39b2f29568f0f81b29ed0abc2/PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {url = "https://files.pythonhosted.org/packages/73/9c/766e78d1efc0d1fca637a6b62cea1b4510a7fb93617eb805223294fef681/PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {url = "https://files.pythonhosted.org/packages/7b/5e/efd033ab7199a0b2044dab3b9f7a4f6670e6a52c089de572e928d2873b06/PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {url = "https://files.pythonhosted.org/packages/7d/39/472f2554a0f1e825bd7c5afc11c817cd7a2f3657460f7159f691fbb37c51/PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {url = "https://files.pythonhosted.org/packages/7f/5d/2779ea035ba1e533c32ed4a249b4e0448f583ba10830b21a3cddafe11a4e/PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {url = "https://files.pythonhosted.org/packages/84/4d/82704d1ab9290b03da94e6425f5e87396b999fd7eb8e08f3a92c158402bf/PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {url = "https://files.pythonhosted.org/packages/96/06/4beb652c0fe16834032e54f0956443d4cc797fe645527acee59e7deaa0a2/PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {url = "https://files.pythonhosted.org/packages/ac/6c/967d91a8edf98d2b2b01d149bd9e51b8f9fb527c98d80ebb60c6b21d60c4/PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {url = "https://files.pythonhosted.org/packages/b3/34/65bb4b2d7908044963ebf614fe0fdb080773fc7030d7e39c8d3eddcd4257/PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {url = "https://files.pythonhosted.org/packages/b6/a0/b6700da5d49e9fed49dc3243d3771b598dad07abb37cc32e524607f96adc/PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {url = "https://files.pythonhosted.org/packages/ba/91/090818dfa62e85181f3ae23dd1e8b7ea7f09684864a900cab72d29c57346/PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {url = "https://files.pythonhosted.org/packages/c1/39/47ed4d65beec9ce07267b014be85ed9c204fa373515355d3efa62d19d892/PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {url = "https://files.pythonhosted.org/packages/c7/d1/02baa09d39b1bb1ebaf0d850d106d1bdcb47c91958557f471153c49dc03b/PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {url = "https://files.pythonhosted.org/packages/c8/6b/6600ac24725c7388255b2f5add93f91e58a5d7efaf4af244fdbcc11a541b/PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {url = "https://files.pythonhosted.org/packages/cc/5c/fcabd17918348c7db2eeeb0575705aaf3f7ab1657f6ce29b2e31737dd5d1/PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {url = "https://files.pythonhosted.org/packages/cd/e5/af35f7ea75cf72f2cd079c95ee16797de7cd71f29ea7c68ae5ce7be1eda0/PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, + {url = "https://files.pythonhosted.org/packages/d6/6a/439d1a6f834b9a9db16332ce16c4a96dd0e3970b65fe08cbecd1711eeb77/PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {url = "https://files.pythonhosted.org/packages/d7/8f/db62b0df635b9008fe90aa68424e99cee05e68b398740c8a666a98455589/PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {url = "https://files.pythonhosted.org/packages/e1/a1/27bfac14b90adaaccf8c8289f441e9f76d94795ec1e7a8f134d9f2cb3d0b/PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {url = "https://files.pythonhosted.org/packages/e5/31/ba812efa640a264dbefd258986a5e4e786230cb1ee4a9f54eb28ca01e14a/PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {url = "https://files.pythonhosted.org/packages/ec/0d/26fb23e8863e0aeaac0c64e03fd27367ad2ae3f3cccf3798ee98ce160368/PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {url = "https://files.pythonhosted.org/packages/f1/26/55e4f21db1f72eaef092015d9017c11510e7e6301c62a6cfee91295d13c6/PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {url = "https://files.pythonhosted.org/packages/fe/88/def2e57fe740544f2eefb1645f1d6e0094f56c00f4eade708140b6137ead/PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, +] +"redis 4.6.0" = [ + {url = "https://files.pythonhosted.org/packages/20/2e/409703d645363352a20c944f5d119bdae3eb3034051a53724a7c5fee12b8/redis-4.6.0-py3-none-any.whl", hash = "sha256:e2b03db868160ee4591de3cb90d40ebb50a90dd302138775937f6a42b7ed183c"}, + {url = "https://files.pythonhosted.org/packages/73/88/63d802c2b18dd9eaa5b846cbf18917c6b2882f20efda398cc16a7500b02c/redis-4.6.0.tar.gz", hash = "sha256:585dc516b9eb042a619ef0a39c3d7d55fe81bdb4df09a52c9cdde0d07bf1aa7d"}, +] +"requests 2.29.0" = [ + {url = "https://files.pythonhosted.org/packages/4c/d2/70fc708727b62d55bc24e43cc85f073039023212d482553d853c44e57bdb/requests-2.29.0.tar.gz", hash = "sha256:f2e34a75f4749019bb0e3effb66683630e4ffeaf75819fb51bebef1bf5aef059"}, + {url = "https://files.pythonhosted.org/packages/cf/e1/2aa539876d9ed0ddc95882451deb57cfd7aa8dbf0b8dbce68e045549ba56/requests-2.29.0-py3-none-any.whl", hash = "sha256:e8f3c9be120d3333921d213eef078af392fba3933ab7ed2d1cba3b56f2568c3b"}, +] +"requests-mock 1.11.0" = [ + {url = "https://files.pythonhosted.org/packages/29/e8/ce73e8d1c7ec127cb3af61df3fd04df9a34eef34e511dc03c069748f773d/requests_mock-1.11.0-py2.py3-none-any.whl", hash = "sha256:f7fae383f228633f6bececebdab236c478ace2284d6292c6e7e2867b9ab74d15"}, + {url = "https://files.pythonhosted.org/packages/c8/7a/fa102fafdc8547ebd744d69d09f4bc314ead1a858fe59d9c2d32591c5f4e/requests-mock-1.11.0.tar.gz", hash = "sha256:ef10b572b489a5f28e09b708697208c4a3b2b89ef80a9f01584340ea357ec3c4"}, +] +"rich 13.5.2" = [ + {url = "https://files.pythonhosted.org/packages/8d/5f/21a93b2ec205f4b79853ff6e838e3c99064d5dbe85ec6b05967506f14af0/rich-13.5.2-py3-none-any.whl", hash = "sha256:146a90b3b6b47cac4a73c12866a499e9817426423f57c5a66949c086191a8808"}, + {url = "https://files.pythonhosted.org/packages/ad/1a/94fe086875350afbd61795c3805e38ef085af466a695db605bcdd34b4c9c/rich-13.5.2.tar.gz", hash = "sha256:fb9d6c0a0f643c99eed3875b5377a184132ba9be4d61516a55273d3554d75a39"}, +] +"rsa 4.9" = [ + {url = "https://files.pythonhosted.org/packages/49/97/fa78e3d2f65c02c8e1268b9aba606569fe97f6c8f7c2d74394553347c145/rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, + {url = "https://files.pythonhosted.org/packages/aa/65/7d973b89c4d2351d7fb232c2e452547ddfa243e93131e7cfa766da627b52/rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, +] +"setuptools 68.1.0" = [ + {url = "https://files.pythonhosted.org/packages/83/b0/18310b45f6eec3757c0554dbc1e03f2f7685c8a87831aea6adc1276faacc/setuptools-68.1.0.tar.gz", hash = "sha256:d59c97e7b774979a5ccb96388efc9eb65518004537e85d52e81eaee89ab6dd91"}, + {url = "https://files.pythonhosted.org/packages/b8/49/b3b29c52b09075fb77f69309763a563b4054d5808a3f3b95df3a62ef3d3f/setuptools-68.1.0-py3-none-any.whl", hash = "sha256:e13e1b0bc760e9b0127eda042845999b2f913e12437046e663b833aa96d89715"}, +] +"six 1.16.0" = [ + {url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, + {url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, +] +"sniffio 1.3.0" = [ + {url = "https://files.pythonhosted.org/packages/c3/a0/5dba8ed157b0136607c7f2151db695885606968d1fae123dc3391e0cfdbf/sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, + {url = "https://files.pythonhosted.org/packages/cd/50/d49c388cae4ec10e8109b1b833fd265511840706808576df3ada99ecb0ac/sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, +] +"sqlalchemy 1.4.41" = [ + {url = "https://files.pythonhosted.org/packages/05/f5/23735f8e87c4c66058b327773654930898cdb3e206a8ddb22aadc2e54cea/SQLAlchemy-1.4.41-cp36-cp36m-win32.whl", hash = "sha256:3e2ef592ac3693c65210f8b53d0edcf9f4405925adcfc031ff495e8d18169682"}, + {url = "https://files.pythonhosted.org/packages/07/0d/46d1a6c25fce13d2c6892e9a203d4baae3058cb04396915365d621965f95/SQLAlchemy-1.4.41-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:639e1ae8d48b3c86ffe59c0daa9a02e2bfe17ca3d2b41611b30a0073937d4497"}, + {url = "https://files.pythonhosted.org/packages/08/a8/8146793f1cbe0b7753463e885dd30ad2f647d700530625598355863397b5/SQLAlchemy-1.4.41-cp37-cp37m-win_amd64.whl", hash = "sha256:5323252be2bd261e0aa3f33cb3a64c45d76829989fa3ce90652838397d84197d"}, + {url = "https://files.pythonhosted.org/packages/10/60/e891b496ca0bbbabedcb387d43be52b6b59dfb902a0e2df26d1cc43caf4c/SQLAlchemy-1.4.41-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2d6495f84c4fd11584f34e62f9feec81bf373787b3942270487074e35cbe5330"}, + {url = "https://files.pythonhosted.org/packages/1b/82/53cc4c827ce330ce97767a3536e320e58f8803da3255ba4752ca20d8f376/SQLAlchemy-1.4.41-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:036d8472356e1d5f096c5e0e1a7e0f9182140ada3602f8fff6b7329e9e7cfbcd"}, + {url = "https://files.pythonhosted.org/packages/1d/46/208bb085d3405eaec7aa41e8b3eda0c3aa596169e0d31c7bcc75ad1b9abc/SQLAlchemy-1.4.41-cp37-cp37m-win32.whl", hash = "sha256:0005bd73026cd239fc1e8ccdf54db58b6193be9a02b3f0c5983808f84862c767"}, + {url = "https://files.pythonhosted.org/packages/37/b5/136c78031fb88f3f79fa1090c339f36a7b9bbb359651767b617f2bbf655a/SQLAlchemy-1.4.41-cp311-cp311-win_amd64.whl", hash = "sha256:d2e054aed4645f9b755db85bc69fc4ed2c9020c19c8027976f66576b906a74f1"}, + {url = "https://files.pythonhosted.org/packages/39/ec/02955ea76aca27cba7b280cea29f7952133f154b3a0be50281f125a4c753/SQLAlchemy-1.4.41-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:c23d64a0b28fc78c96289ffbd0d9d1abd48d267269b27f2d34e430ea73ce4b26"}, + {url = "https://files.pythonhosted.org/packages/42/8b/4ddf009cb17231471419d9e31dd03005c0b31f8a4e94a9cd1a0b4ade44d4/SQLAlchemy-1.4.41-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:4ba7e122510bbc07258dc42be6ed45997efdf38129bde3e3f12649be70683546"}, + {url = "https://files.pythonhosted.org/packages/5b/05/0344b99768d345cd92785949a3dac38bfb7059b3b4dc6ae1e55ea842c772/SQLAlchemy-1.4.41-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:361f6b5e3f659e3c56ea3518cf85fbdae1b9e788ade0219a67eeaaea8a4e4d2a"}, + {url = "https://files.pythonhosted.org/packages/5b/3d/4c6da7a76f850c55e9115d5bcf2f90509a8617f4e955d9bd82f23008e029/SQLAlchemy-1.4.41-cp38-cp38-win32.whl", hash = "sha256:58bb65b3274b0c8a02cea9f91d6f44d0da79abc993b33bdedbfec98c8440175a"}, + {url = "https://files.pythonhosted.org/packages/5c/0c/4256c722fc41e7f581776ac05af9b5db5c304c7888d625e47d079024c7b8/SQLAlchemy-1.4.41-cp38-cp38-win_amd64.whl", hash = "sha256:ce8feaa52c1640de9541eeaaa8b5fb632d9d66249c947bb0d89dd01f87c7c288"}, + {url = "https://files.pythonhosted.org/packages/67/a0/97da2cb07e013fd6c37fd896a86b374aa726e4161cafd57185e8418d59aa/SQLAlchemy-1.4.41.tar.gz", hash = "sha256:0292f70d1797e3c54e862e6f30ae474014648bc9c723e14a2fda730adb0a9791"}, + {url = "https://files.pythonhosted.org/packages/73/2e/d61aeec5580ae1841508c39ac63a9a8cfb8200d88f3d9b7d57607ab2f245/SQLAlchemy-1.4.41-cp39-cp39-win_amd64.whl", hash = "sha256:f5fa526d027d804b1f85cdda1eb091f70bde6fb7d87892f6dd5a48925bc88898"}, + {url = "https://files.pythonhosted.org/packages/79/5f/cf2664ea15b04cfacab5f9ed791741874c67d58f69ad86c22488bc53a2f0/SQLAlchemy-1.4.41-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:14576238a5f89bcf504c5f0a388d0ca78df61fb42cb2af0efe239dc965d4f5c9"}, + {url = "https://files.pythonhosted.org/packages/7e/7f/0693241547e0b8534600e831dfe0a8bbcb29a60c53925ed604a747a00bb8/SQLAlchemy-1.4.41-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e16c2be5cb19e2c08da7bd3a87fed2a0d4e90065ee553a940c4fc1a0fb1ab72b"}, + {url = "https://files.pythonhosted.org/packages/85/8a/83f1056449d819532c337a4a1b709a8e6291b9398340c0b2c00d5fdc7589/SQLAlchemy-1.4.41-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:05f0de3a1dc3810a776275763764bb0015a02ae0f698a794646ebc5fb06fad33"}, + {url = "https://files.pythonhosted.org/packages/93/0c/377daa276fa54ad65a6dbd0323285cf0892972fa88a4dbe17113ec440c32/SQLAlchemy-1.4.41-cp311-cp311-win32.whl", hash = "sha256:59bdc291165b6119fc6cdbc287c36f7f2859e6051dd923bdf47b4c55fd2f8bd0"}, + {url = "https://files.pythonhosted.org/packages/a8/62/9f74f13f3907ca416d8fc7b1c33a8137717a2a2d42364038b9437dcc8040/SQLAlchemy-1.4.41-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0002e829142b2af00b4eaa26c51728f3ea68235f232a2e72a9508a3116bd6ed0"}, + {url = "https://files.pythonhosted.org/packages/b1/1a/e0c11a28c2d2c3c1e74705d4fcb2246434050eed69b70e6acf0ef88adbb0/SQLAlchemy-1.4.41-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:22ff16cedab5b16a0db79f1bc99e46a6ddececb60c396562e50aab58ddb2871c"}, + {url = "https://files.pythonhosted.org/packages/b6/df/51a99ba9b419e15aa39948756f79d6ef2df9ede3288799c1deb43b618799/SQLAlchemy-1.4.41-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5102fb9ee2c258a2218281adcb3e1918b793c51d6c2b4666ce38c35101bb940e"}, + {url = "https://files.pythonhosted.org/packages/bc/a9/f9eb3d4952bfa67f7489732af8db2c31b2e99b6b2f70f786fb6d92b18ebb/SQLAlchemy-1.4.41-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:199a73c31ac8ea59937cc0bf3dfc04392e81afe2ec8a74f26f489d268867846c"}, + {url = "https://files.pythonhosted.org/packages/be/76/912622f9e0b87a9fc58d4d58e9ce459bbd9cd83021c51989afb1839d2162/SQLAlchemy-1.4.41-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0990932f7cca97fece8017414f57fdd80db506a045869d7ddf2dda1d7cf69ecc"}, + {url = "https://files.pythonhosted.org/packages/bf/ed/443a8584b15cbab97f0a5e5ba4974c7b6c989d2ec5a37423946a24619bcf/SQLAlchemy-1.4.41-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8eb8897367a21b578b26f5713833836f886817ee2ffba1177d446fa3f77e67c8"}, + {url = "https://files.pythonhosted.org/packages/bf/f2/69c9f96515b4eb65fac522c8b81ec10666ee4789484b0c123452c1f22505/SQLAlchemy-1.4.41-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ad2b727fc41c7f8757098903f85fafb4bf587ca6605f82d9bf5604bd9c7cded"}, + {url = "https://files.pythonhosted.org/packages/ce/b7/1b65516236b36b55624768f7923c9a8d55ca4ba239b795ea84cb82086718/SQLAlchemy-1.4.41-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2307495d9e0ea00d0c726be97a5b96615035854972cc538f6e7eaed23a35886c"}, + {url = "https://files.pythonhosted.org/packages/d0/ea/86e73fb946694c491a332710d0686f3260b941b3af43502457d3a62512dd/SQLAlchemy-1.4.41-cp310-cp310-win32.whl", hash = "sha256:2082a2d2fca363a3ce21cfa3d068c5a1ce4bf720cf6497fb3a9fc643a8ee4ddd"}, + {url = "https://files.pythonhosted.org/packages/d5/4a/29ce9d2ec5bb2d3e83ad387b956defde6229252259795cd28210a5020740/SQLAlchemy-1.4.41-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5ebeeec5c14533221eb30bad716bc1fd32f509196318fb9caa7002c4a364e4c"}, + {url = "https://files.pythonhosted.org/packages/d6/b7/78d3425a6b3aa486c46259228c1933a22ac4d48b0e6220930973ac852091/SQLAlchemy-1.4.41-cp310-cp310-win_amd64.whl", hash = "sha256:e4b12e3d88a8fffd0b4ca559f6d4957ed91bd4c0613a4e13846ab8729dc5c251"}, + {url = "https://files.pythonhosted.org/packages/de/c2/cb1e60fee76b253b396e31a641e117ba689437b1d9dbecfe8415cb0e8b43/SQLAlchemy-1.4.41-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:13e397a9371ecd25573a7b90bd037db604331cf403f5318038c46ee44908c44d"}, + {url = "https://files.pythonhosted.org/packages/e4/3c/b37bbfe25ebfe129cfa7843e74af3081cca6ae9a893869ba82639479fdf9/SQLAlchemy-1.4.41-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0dcf127bb99458a9d211e6e1f0f3edb96c874dd12f2503d4d8e4f1fd103790b"}, + {url = "https://files.pythonhosted.org/packages/e5/5b/fbaf9a5f3ef900f9eb30644cb74520a7771250a1d0b26a44ca053d3ef4fe/SQLAlchemy-1.4.41-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676d51c9f6f6226ae8f26dc83ec291c088fe7633269757d333978df78d931ab"}, + {url = "https://files.pythonhosted.org/packages/ea/4e/4bcd7e756fa2e989e7eed239bca3c3fc57101b7d0c49864f8e41d202d1ce/SQLAlchemy-1.4.41-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b67fc780cfe2b306180e56daaa411dd3186bf979d50a6a7c2a5b5036575cbdbb"}, + {url = "https://files.pythonhosted.org/packages/f0/97/c6a1bc6e80844c10ee1cb599fa5d8c919fc68b9d9ebed22217cadcfca4c8/SQLAlchemy-1.4.41-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cd767cf5d7252b1c88fcfb58426a32d7bd14a7e4942497e15b68ff5d822b41ad"}, + {url = "https://files.pythonhosted.org/packages/f1/81/638d6bd19baf595959c42c154d83262d609140898eb88866db2f024fcc00/SQLAlchemy-1.4.41-cp39-cp39-win32.whl", hash = "sha256:9c56e19780cd1344fcd362fd6265a15f48aa8d365996a37fab1495cae8fcd97d"}, + {url = "https://files.pythonhosted.org/packages/f4/06/78ab18ec859c7dbdb5182b8463ebb3abac932ad086b9dd15fb60958f9a4f/SQLAlchemy-1.4.41-cp27-cp27m-win_amd64.whl", hash = "sha256:5facb7fd6fa8a7353bbe88b95695e555338fb038ad19ceb29c82d94f62775a05"}, + {url = "https://files.pythonhosted.org/packages/f6/ca/6d666434176ff264e750d14b833a7f2243183a8a69f3a25253f1f0052f09/SQLAlchemy-1.4.41-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ccfd238f766a5bb5ee5545a62dd03f316ac67966a6a658efb63eeff8158a4bbf"}, + {url = "https://files.pythonhosted.org/packages/f8/84/f92a2de0e4a7e82acca2bc74c75295fe5f141ea8ba002e2218cea41d2245/SQLAlchemy-1.4.41-cp36-cp36m-win_amd64.whl", hash = "sha256:eb30cf008850c0a26b72bd1b9be6730830165ce049d239cfdccd906f2685f892"}, + {url = "https://files.pythonhosted.org/packages/fa/5f/150ca2e971231624041de73fbc61b0b16f5139530cbff889213cc00f83f8/SQLAlchemy-1.4.41-cp27-cp27m-win32.whl", hash = "sha256:e570cfc40a29d6ad46c9aeaddbdcee687880940a3a327f2c668dd0e4ef0a441d"}, + {url = "https://files.pythonhosted.org/packages/fe/28/f22792eee334cd83a15ef34b825761ee057d330b9b24d3f1496b95faa557/SQLAlchemy-1.4.41-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:90484a2b00baedad361402c257895b13faa3f01780f18f4a104a2f5c413e4536"}, + {url = "https://files.pythonhosted.org/packages/ff/1c/55bf52c1961ce01164835047ed2c09e44b76d1f18a75841715626f2786b1/SQLAlchemy-1.4.41-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:f37fa70d95658763254941ddd30ecb23fc4ec0c5a788a7c21034fc2305dab7cc"}, +] +"sqlalchemy-utils 0.41.1" = [ + {url = "https://files.pythonhosted.org/packages/73/d8/3863fdfe6b27f6c0dffc650aaa2929f313b33aea615b102279fd46ab550b/SQLAlchemy_Utils-0.41.1-py3-none-any.whl", hash = "sha256:6c96b0768ea3f15c0dc56b363d386138c562752b84f647fb8d31a2223aaab801"}, + {url = "https://files.pythonhosted.org/packages/a3/e0/6906a8a9b8e9deb82923e02e2c1f750c567d69a34f6e1fe566792494a682/SQLAlchemy-Utils-0.41.1.tar.gz", hash = "sha256:a2181bff01eeb84479e38571d2c0718eb52042f9afd8c194d0d02877e84b7d74"}, +] +"sqlalchemy2-stubs 0.0.2a35" = [ + {url = "https://files.pythonhosted.org/packages/bd/a6/289f42af833bf4e6d14e416f79cdeada07d2e5a37fcdd8e469b535fd8fd6/sqlalchemy2_stubs-0.0.2a35-py3-none-any.whl", hash = "sha256:593784ff9fc0dc2ded1895e3322591689db3be06f3ca006e3ef47640baf2d38a"}, + {url = "https://files.pythonhosted.org/packages/c0/70/42d1281f0ea2f5cefea976e6dbd691aea179a26498402d682af180e58b9a/sqlalchemy2-stubs-0.0.2a35.tar.gz", hash = "sha256:bd5d530697d7e8c8504c7fe792ef334538392a5fb7aa7e4f670bfacdd668a19d"}, +] +"sqlmodel 0.0.8" = [ + {url = "https://files.pythonhosted.org/packages/64/ba/ad07004536e94e71f99aaae5e667bb6f7230f7e0fbc0b0266e88960dda5f/sqlmodel-0.0.8.tar.gz", hash = "sha256:3371b4d1ad59d2ffd0c530582c2140b6c06b090b32af9b9c6412986d7b117036"}, + {url = "https://files.pythonhosted.org/packages/90/63/65f95cf5902ccdfccec99de87666b5e039589c19db7ab62b3770171e5685/sqlmodel-0.0.8-py3-none-any.whl", hash = "sha256:0fd805719e0c5d4f22be32eb3ffc856eca3f7f20e8c7aa3e117ad91684b518ee"}, +] +"sqlparse 0.4.4" = [ + {url = "https://files.pythonhosted.org/packages/65/16/10f170ec641ed852611b6c9441b23d10b5702ab5288371feab3d36de2574/sqlparse-0.4.4.tar.gz", hash = "sha256:d446183e84b8349fa3061f0fe7f06ca94ba65b426946ffebe6e3e8295332420c"}, + {url = "https://files.pythonhosted.org/packages/98/5a/66d7c9305baa9f11857f247d4ba761402cea75db6058ff850ed7128957b7/sqlparse-0.4.4-py3-none-any.whl", hash = "sha256:5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3"}, +] +"sse-starlette 1.6.5" = [ + {url = "https://files.pythonhosted.org/packages/e9/83/5825dd7be255bbd3587a45651804092817eadf7070db757331d0e9c2ee47/sse_starlette-1.6.5-py3-none-any.whl", hash = "sha256:68b6b7eb49be0c72a2af80a055994c13afcaa4761b29226beb208f954c25a642"}, + {url = "https://files.pythonhosted.org/packages/fa/51/f504bda4774c2c8abfa7ab9ef8f1f6f23838e39c1ae4b9185083fe4de5de/sse-starlette-1.6.5.tar.gz", hash = "sha256:819f2c421fb37067380fe3dcaba246c476b02651b7bb7601099a378ad802a0ac"}, +] +"starlette 0.19.1" = [ + {url = "https://files.pythonhosted.org/packages/2b/18/405f4fb59119b8efa203c10a04a32a927976b5450cf649c8b4c9d079d21e/starlette-0.19.1.tar.gz", hash = "sha256:c6d21096774ecb9639acad41b86b7706e52ba3bf1dc13ea4ed9ad593d47e24c7"}, + {url = "https://files.pythonhosted.org/packages/f1/9d/1fa96008b302dd3e398f89f3fc5afb19fb0b0f341fefa05c65b3a38d64cf/starlette-0.19.1-py3-none-any.whl", hash = "sha256:5a60c5c2d051f3a8eb546136aa0c9399773a689595e099e0877704d5888279bf"}, +] +"strawberry-graphql 0.204.0" = [ + {url = "https://files.pythonhosted.org/packages/0a/43/4d271af51c3377ce9966bdc72ab0901bc5831fbcffd0b23bdaf594e40a64/strawberry_graphql-0.204.0.tar.gz", hash = "sha256:2c806036ecfe6d32cc0bae111346d9df24a7ca7156cc61047ce429efbb6cd630"}, + {url = "https://files.pythonhosted.org/packages/be/bb/abe69462a27eb1cabb3ab64d9fdc98c7f15f08fe5ae30d1474148ce11c7f/strawberry_graphql-0.204.0-py3-none-any.whl", hash = "sha256:e5f46abf49e7569335b51e992fb54f040355712274cf74a8bd540dd692457ccd"}, +] +"tomli 2.0.1" = [ + {url = "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {url = "https://files.pythonhosted.org/packages/c0/3f/d7af728f075fb08564c5949a9c95e44352e23dee646869fa104a3b2060a3/tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] +"tomlkit 0.12.1" = [ + {url = "https://files.pythonhosted.org/packages/0d/07/d34a911a98e64b07f862da4b10028de0c1ac2222ab848eaf5dd1877c4b1b/tomlkit-0.12.1.tar.gz", hash = "sha256:38e1ff8edb991273ec9f6181244a6a391ac30e9f5098e7535640ea6be97a7c86"}, + {url = "https://files.pythonhosted.org/packages/a0/6d/808775ed618e51edaa7bbe6759e22e1c7eafe359af6e084700c6d39d3455/tomlkit-0.12.1-py3-none-any.whl", hash = "sha256:712cbd236609acc6a3e2e97253dfc52d4c2082982a88f61b640ecf0817eab899"}, +] +"types-cachetools 5.3.0.6" = [ + {url = "https://files.pythonhosted.org/packages/3a/04/cb753a05dfb30ed9e0eaa0fb447761d3399051eab1c618f5347f4339f364/types_cachetools-5.3.0.6-py3-none-any.whl", hash = "sha256:f7f8a25bfe306f2e6bc2ad0a2f949d9e72f2d91036d509c36d3810bf728bc6e1"}, + {url = "https://files.pythonhosted.org/packages/cf/85/78e40815bd412b39216edff562abb0c5614b71e395bae97f8332c6de661d/types-cachetools-5.3.0.6.tar.gz", hash = "sha256:595f0342d246c8ba534f5a762cf4c2f60ecb61e8002b8b2277fd5cf791d4e851"}, +] +"typing-extensions 4.7.1" = [ + {url = "https://files.pythonhosted.org/packages/3c/8b/0111dd7d6c1478bf83baa1cab85c686426c7a6274119aceb2bd9d35395ad/typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, + {url = "https://files.pythonhosted.org/packages/ec/6b/63cc3df74987c36fe26157ee12e09e8f9db4de771e0f3404263117e75b95/typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, +] +"tzdata 2023.3" = [ + {url = "https://files.pythonhosted.org/packages/70/e5/81f99b9fced59624562ab62a33df639a11b26c582be78864b339dafa420d/tzdata-2023.3.tar.gz", hash = "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a"}, + {url = "https://files.pythonhosted.org/packages/d5/fb/a79efcab32b8a1f1ddca7f35109a50e4a80d42ac1c9187ab46522b2407d7/tzdata-2023.3-py2.py3-none-any.whl", hash = "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"}, +] +"urllib3 1.26.16" = [ + {url = "https://files.pythonhosted.org/packages/c5/05/c214b32d21c0b465506f95c4f28ccbcba15022e000b043b72b3df7728471/urllib3-1.26.16-py2.py3-none-any.whl", hash = "sha256:8d36afa7616d8ab714608411b4a3b13e58f463aee519024578e062e141dce20f"}, + {url = "https://files.pythonhosted.org/packages/e2/7d/539e6f0cf9f0b95b71dd701a56dae89f768cd39fd8ce0096af3546aeb5a3/urllib3-1.26.16.tar.gz", hash = "sha256:8f135f6502756bde6b2a9b28989df5fbe87c9970cecaa69041edcce7f0589b14"}, +] +"uvicorn 0.23.2" = [ + {url = "https://files.pythonhosted.org/packages/4c/b3/aa7eb8367959623eef0527f876e371f1ac5770a3b31d3d6db34337b795e6/uvicorn-0.23.2.tar.gz", hash = "sha256:4d3cc12d7727ba72b64d12d3cc7743124074c0a69f7b201512fc50c3e3f1569a"}, + {url = "https://files.pythonhosted.org/packages/79/96/b0882a1c3f7ef3dd86879e041212ae5b62b4bd352320889231cc735a8e8f/uvicorn-0.23.2-py3-none-any.whl", hash = "sha256:1f9be6558f01239d4fdf22ef8126c39cb1ad0addf76c40e760549d2c2f43ab53"}, +] +"uvloop 0.17.0" = [ + {url = "https://files.pythonhosted.org/packages/04/e3/e8c6b6b2ece6b0ab6033c62344d3de1706ed773d10c1798ee8afb0007b8c/uvloop-0.17.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8efcadc5a0003d3a6e887ccc1fb44dec25594f117a94e3127954c05cf144d811"}, + {url = "https://files.pythonhosted.org/packages/08/f2/99ea33be2a601d74b345605f4843f678b8fc19b6b348c0cf07883791f0b2/uvloop-0.17.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c092a2c1e736086d59ac8e41f9c98f26bbf9b9222a76f21af9dfe949b99b2eb9"}, + {url = "https://files.pythonhosted.org/packages/0e/27/f4f8afa5f34626f5e4fdd6b96734546d293dfe3593a6d73a8785c3e79817/uvloop-0.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cbbe908fda687e39afd6ea2a2f14c2c3e43f2ca88e3a11964b297822358d0e6c"}, + {url = "https://files.pythonhosted.org/packages/13/12/58a06670863b147f2b5bcd35ec16e55c2e811a67e926f62b4c04e6f52755/uvloop-0.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6aafa5a78b9e62493539456f8b646f85abc7093dd997f4976bb105537cf2635e"}, + {url = "https://files.pythonhosted.org/packages/14/58/333a56082bf25dee13cf9e8de5f408d107d75bf6145835ec6d6b2fd35980/uvloop-0.17.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3378eb62c63bf336ae2070599e49089005771cc651c8769aaad72d1bd9385a7c"}, + {url = "https://files.pythonhosted.org/packages/20/9b/920b4b52028a84cc6031b4ce4bef1077d3475e6ce87969a0f0d220807307/uvloop-0.17.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff3d00b70ce95adce264462c930fbaecb29718ba6563db354608f37e49e09024"}, + {url = "https://files.pythonhosted.org/packages/2b/6f/ec3a30f0de00b8d240ab2128d50e4bf20b263065bc51eb0b4bbfaae6c87d/uvloop-0.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c686a47d57ca910a2572fddfe9912819880b8765e2f01dc0dd12a9bf8573e539"}, + {url = "https://files.pythonhosted.org/packages/2c/08/c76bc0325b1a372e6780a169c1da56117591335a08ee19c09e3e6839a195/uvloop-0.17.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7d37dccc7ae63e61f7b96ee2e19c40f153ba6ce730d8ba4d3b4e9738c1dccc1b"}, + {url = "https://files.pythonhosted.org/packages/2c/70/c4162951c8c3a4a8b19a62b2668517e16b4e74499e040c07c7d99dad5126/uvloop-0.17.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8887d675a64cfc59f4ecd34382e5b4f0ef4ae1da37ed665adba0c2badf0d6578"}, + {url = "https://files.pythonhosted.org/packages/33/f5/94d267b8286fd9390a3276843300461edaa65431b428634056994b24b16a/uvloop-0.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0949caf774b9fcefc7c5756bacbbbd3fc4c05a6b7eebc7c7ad6f825b23998d6d"}, + {url = "https://files.pythonhosted.org/packages/5b/68/08d63f6e426fdb18d718251de786e784254985f633bbd16685e0befb5b04/uvloop-0.17.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3db8de10ed684995a7f34a001f15b374c230f7655ae840964d51496e2f8a8474"}, + {url = "https://files.pythonhosted.org/packages/5d/bc/c1ef0b1c8faa3960b22f5809ebfd1eaa009e441b28b697f8871c31fc51d7/uvloop-0.17.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6708f30db9117f115eadc4f125c2a10c1a50d711461699a0cbfaa45b9a78e376"}, + {url = "https://files.pythonhosted.org/packages/7f/17/e300f183e5cbcc197eaa62c0c020072b778039297b0df896b6274a73a7da/uvloop-0.17.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45cea33b208971e87a31c17622e4b440cac231766ec11e5d22c76fab3bf9df62"}, + {url = "https://files.pythonhosted.org/packages/83/c0/9ade5760e31bc67fc30e74cf896cc72f7f8f8121b0ac64113c684571a22b/uvloop-0.17.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a5abddb3558d3f0a78949c750644a67be31e47936042d4f6c888dd6f3c95f4aa"}, + {url = "https://files.pythonhosted.org/packages/88/0b/f795eeada85d2971b0718a45683e673ad2211ba8d68b166d1f917fc0b86f/uvloop-0.17.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:9b09e0f0ac29eee0451d71798878eae5a4e6a91aa275e114037b27f7db72702d"}, + {url = "https://files.pythonhosted.org/packages/8a/ff/bb80345a3fc39b0ce1ad27e8906874337a29dfb77e6d1e26740439be4a93/uvloop-0.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ebeeec6a6641d0adb2ea71dcfb76017602ee2bfd8213e3fcc18d8f699c5104f"}, + {url = "https://files.pythonhosted.org/packages/8f/93/6e0ce46158943650c6f15c4acfb008d9314fe670a1376399cdea295bf71e/uvloop-0.17.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a4aee22ece20958888eedbad20e4dbb03c37533e010fb824161b4f05e641f738"}, + {url = "https://files.pythonhosted.org/packages/90/75/e856169afc8c4676402a2c45ecb409f25e3dca4e17a5291bf6804006deba/uvloop-0.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:68532f4349fd3900b839f588972b3392ee56042e440dd5873dfbbcd2cc67617c"}, + {url = "https://files.pythonhosted.org/packages/93/f8/5ba5eb1e005e2419d455d8d677211bf58ba500f204236e0b089c1a6067be/uvloop-0.17.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:23609ca361a7fc587031429fa25ad2ed7242941adec948f9d10c045bfecab06b"}, + {url = "https://files.pythonhosted.org/packages/97/ae/e60b67eca95e9bf8f3407996acc478a8df2a0cda4cce5c3d231a831d79ba/uvloop-0.17.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:30babd84706115626ea78ea5dbc7dd8d0d01a2e9f9b306d24ca4ed5796c66ded"}, + {url = "https://files.pythonhosted.org/packages/a9/17/e0a10e6b5a1ace1861ba496981fed35dd806c81fa18260e6e631f2713c3c/uvloop-0.17.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2a6149e1defac0faf505406259561bc14b034cdf1d4711a3ddcdfbaa8d825a05"}, + {url = "https://files.pythonhosted.org/packages/ab/03/ed3a0d08c9d307e8babdbed7fc6c54b273602adb3fa41748b6c1785108b3/uvloop-0.17.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1e507c9ee39c61bfddd79714e4f85900656db1aec4d40c6de55648e85c2799c"}, + {url = "https://files.pythonhosted.org/packages/ad/14/f791682bc94a80b03431de5d753484ac1c8a5cc3b966fd21f053ad14d5c8/uvloop-0.17.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ce9f61938d7155f79d3cb2ffa663147d4a76d16e08f65e2c66b77bd41b356718"}, + {url = "https://files.pythonhosted.org/packages/b1/0c/f08c6863c9e0a6823b69fbbc6753a3e4f47c3a48628ce6e8370bd39b76e7/uvloop-0.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:864e1197139d651a76c81757db5eb199db8866e13acb0dfe96e6fc5d1cf45fc4"}, + {url = "https://files.pythonhosted.org/packages/ba/86/6dda1760481abf244cbd3908b79a4520d757040ca9ec37a79fc0fd01e2a0/uvloop-0.17.0.tar.gz", hash = "sha256:0ddf6baf9cf11a1a22c71487f39f15b2cf78eb5bde7e5b45fbb99e8a9d91b9e1"}, + {url = "https://files.pythonhosted.org/packages/c5/56/745a5e615edbec0e6062397782285fbb01c50bf659e2b22489bdd9f9318f/uvloop-0.17.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:307958f9fc5c8bb01fad752d1345168c0abc5d62c1b72a4a8c6c06f042b45b20"}, + {url = "https://files.pythonhosted.org/packages/c6/b3/60fc0f21b58b86335e2435b2cd6a9d75cb79d99787f15663fae01406c8c5/uvloop-0.17.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2deae0b0fb00a6af41fe60a675cec079615b01d68beb4cc7b722424406b126a8"}, + {url = "https://files.pythonhosted.org/packages/d3/85/2fea43f570b32027dbf11426ea88aea9e4525f40f6e0b7017a74ab7d57ad/uvloop-0.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d97672dc709fa4447ab83276f344a165075fd9f366a97b712bdd3fee05efae8"}, + {url = "https://files.pythonhosted.org/packages/fa/28/8a3c2f067014018ba6647c39af64e3b45e5391cf85ba882fa824bda9dba3/uvloop-0.17.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dbbaf9da2ee98ee2531e0c780455f2841e4675ff580ecf93fe5c48fe733b5667"}, + {url = "https://files.pythonhosted.org/packages/fb/11/fef3cf9f2aa23a7daf84c39dbd66dcd562479ffc2c064496d0525adc4b43/uvloop-0.17.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1436c8673c1563422213ac6907789ecb2b070f5939b9cbff9ef7113f2b531595"}, +] +"vine 5.0.0" = [ + {url = "https://files.pythonhosted.org/packages/66/b2/8954108816865edf2b1e0d24f3c2c11dfd7232f795bcf1e4164fb8ee5e15/vine-5.0.0.tar.gz", hash = "sha256:7d3b1624a953da82ef63462013bbd271d3eb75751489f9807598e8f340bd637e"}, + {url = "https://files.pythonhosted.org/packages/8d/61/a7badb48186919a9fd7cf0ef427cab6d16e0ed474035c36fa64ddd72bfa2/vine-5.0.0-py2.py3-none-any.whl", hash = "sha256:4c9dceab6f76ed92105027c49c823800dd33cacce13bdedc5b914e3514b7fb30"}, +] +"virtualenv 20.24.3" = [ + {url = "https://files.pythonhosted.org/packages/17/8d/6989e5dcd812520cbf9f31be2b08643ae3a895586601bbab501df8ed6e54/virtualenv-20.24.3-py3-none-any.whl", hash = "sha256:95a6e9398b4967fbcb5fef2acec5efaf9aa4972049d9ae41f95e0972a683fd02"}, + {url = "https://files.pythonhosted.org/packages/77/f9/f6319b17869e66571966060051894d7a6dc77feceb25a9ebb6daee7eed5a/virtualenv-20.24.3.tar.gz", hash = "sha256:e5c3b4ce817b0b328af041506a2a299418c98747c4b1e68cb7527e74ced23efc"}, +] +"watchfiles 0.19.0" = [ + {url = "https://files.pythonhosted.org/packages/09/82/a9e1b9741cefa592dcd85f8ebdf739a24c6572b5ab58ce65589f340b3cff/watchfiles-0.19.0-cp37-abi3-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:18b28f6ad871b82df9542ff958d0c86bb0d8310bb09eb8e87d97318a3b5273af"}, + {url = "https://files.pythonhosted.org/packages/1e/68/86742189038396f0b8df17556690c5fdb350e74c2b947782a165c6f69acb/watchfiles-0.19.0-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:cae3dde0b4b2078f31527acff6f486e23abed307ba4d3932466ba7cdd5ecec79"}, + {url = "https://files.pythonhosted.org/packages/29/8a/6293b04bcb667e43808921cc8794f6231aa4ea819d0cdb659b3e3cf862f1/watchfiles-0.19.0-cp37-abi3-win_arm64.whl", hash = "sha256:0089c6dc24d436b373c3c57657bf4f9a453b13767150d17284fc6162b2791911"}, + {url = "https://files.pythonhosted.org/packages/2f/7f/0fb6699188e4a553e10e94a479fe4280a17d29f3a8b8b8f11d6291a82006/watchfiles-0.19.0-cp37-abi3-macosx_10_7_x86_64.whl", hash = "sha256:91633e64712df3051ca454ca7d1b976baf842d7a3640b87622b323c55f3345e7"}, + {url = "https://files.pythonhosted.org/packages/47/6a/de51bae85bcbf17dc9389589162c8996ed72bf6e97b81c8c6d60666e4768/watchfiles-0.19.0-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:79c533ff593db861ae23436541f481ec896ee3da4e5db8962429b441bbaae16e"}, + {url = "https://files.pythonhosted.org/packages/50/0e/026461f88bf5dafd9bfee6fcf7fe2deddece9d3e0c86d4b7986b0e7c5df3/watchfiles-0.19.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9afd0d69429172c796164fd7fe8e821ade9be983f51c659a38da3faaaaac44dc"}, + {url = "https://files.pythonhosted.org/packages/64/22/88e5143e4397bf30cde669b78fb2a2fcdbaaa0e8b5fea5ae402d2e5a7bc2/watchfiles-0.19.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f3920b1285a7d3ce898e303d84791b7bf40d57b7695ad549dc04e6a44c9f120"}, + {url = "https://files.pythonhosted.org/packages/66/1c/5b14d4be9f02a96c2f06b92d9bc3335ced1f4e1c7ea6e522fa4c9a517f28/watchfiles-0.19.0-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:3d7d267d27aceeeaa3de0dd161a0d64f0a282264d592e335fff7958cc0cbae7c"}, + {url = "https://files.pythonhosted.org/packages/79/65/08d06f63c2d983c733e225b9cd2ba9f36b7ccb1095854b66d0010ca36013/watchfiles-0.19.0-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c0376deac92377817e4fb8f347bf559b7d44ff556d9bc6f6208dd3f79f104aaf"}, + {url = "https://files.pythonhosted.org/packages/8a/1c/6869ad3639b23b3a88ba5a61aacffaf425666e416cd4e0d8b6b47e666a68/watchfiles-0.19.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b538014a87f94d92f98f34d3e6d2635478e6be6423a9ea53e4dd96210065e193"}, + {url = "https://files.pythonhosted.org/packages/8e/e1/85f1454e0c6335e32d1bd900237d34f76db2b261a515bc47188d8e3eea65/watchfiles-0.19.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:b6577b8c6c8701ba8642ea9335a129836347894b666dd1ec2226830e263909d3"}, + {url = "https://files.pythonhosted.org/packages/92/e5/a77ee68cbeedf342f317214a5b48f4c191d021b57836ef3b2c39bcbb516c/watchfiles-0.19.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fac19dc9cbc34052394dbe81e149411a62e71999c0a19e1e09ce537867f95ae0"}, + {url = "https://files.pythonhosted.org/packages/9b/51/104178107563657a7fe5c5d3a0426fa5f61f837698bc349bc93761ce07a5/watchfiles-0.19.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68dce92b29575dda0f8d30c11742a8e2b9b8ec768ae414b54f7453f27bdf9545"}, + {url = "https://files.pythonhosted.org/packages/9d/d1/708fc7475e33990792262f23d054692da98b4b8c847772ca12d868d55ac5/watchfiles-0.19.0-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:5569fc7f967429d4bc87e355cdfdcee6aabe4b620801e2cf5805ea245c06097c"}, + {url = "https://files.pythonhosted.org/packages/aa/3b/c648597eb3611c4b72eda09aa9069773f6b4459bd7142cf200dcd34acfcf/watchfiles-0.19.0-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c75eff897786ee262c9f17a48886f4e98e6cfd335e011c591c305e5d083c056"}, + {url = "https://files.pythonhosted.org/packages/ad/5d/bc89d194bac1baafa4e98bade4d778d5b2918e4b9cc45284ec78c90b135c/watchfiles-0.19.0-cp37-abi3-win32.whl", hash = "sha256:176a9a7641ec2c97b24455135d58012a5be5c6217fc4d5fef0b2b9f75dbf5154"}, + {url = "https://files.pythonhosted.org/packages/b0/6a/a89df14975f21b41d199f745d016dd6cebce232cb7634d3cd441ca67a1eb/watchfiles-0.19.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5471582658ea56fca122c0f0d0116a36807c63fefd6fdc92c71ca9a4491b6b48"}, + {url = "https://files.pythonhosted.org/packages/b1/95/5fa75076399195838c618e06418dbc4996613e3e57fbecd0c1d86764afbf/watchfiles-0.19.0-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:09ea3397aecbc81c19ed7f025e051a7387feefdb789cf768ff994c1228182fda"}, + {url = "https://files.pythonhosted.org/packages/b2/da/be8664576654aa13312d5b9e854b9c6b7f759125119e5b69e6cdc74deffd/watchfiles-0.19.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20b44221764955b1e703f012c74015306fb7e79a00c15370785f309b1ed9aa8d"}, + {url = "https://files.pythonhosted.org/packages/b3/17/d9453f774dd079fbe7d51565d58006f5059fc17c2fbcf952ef176fbb8657/watchfiles-0.19.0.tar.gz", hash = "sha256:d9b073073e048081e502b6c6b0b88714c026a1a4c890569238d04aca5f9ca74b"}, + {url = "https://files.pythonhosted.org/packages/f2/40/cd125248cd1bb1fd449d2439d9a1b599c9f6dc255645838ce0be96b28337/watchfiles-0.19.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb5d45c4143c1dd60f98a16187fd123eda7248f84ef22244818c18d531a249d1"}, + {url = "https://files.pythonhosted.org/packages/fe/1d/3948e864ae4f4c87cd32d980aff9b7b8767f74e0d8ea0b70a07a86226087/watchfiles-0.19.0-cp37-abi3-win_amd64.whl", hash = "sha256:945be0baa3e2440151eb3718fd8846751e8b51d8de7b884c90b17d271d34cae8"}, +] +"wcwidth 0.2.6" = [ + {url = "https://files.pythonhosted.org/packages/20/f4/c0584a25144ce20bfcf1aecd041768b8c762c1eb0aa77502a3f0baa83f11/wcwidth-0.2.6-py2.py3-none-any.whl", hash = "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e"}, + {url = "https://files.pythonhosted.org/packages/5e/5f/1e4bd82a9cc1f17b2c2361a2d876d4c38973a997003ba5eb400e8a932b6c/wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"}, +] +"websockets 11.0.3" = [ + {url = "https://files.pythonhosted.org/packages/03/28/3a51ffcf51ac45746639f83128908bbb1cd212aa631e42d15a7acebce5cb/websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e052b8467dd07d4943936009f46ae5ce7b908ddcac3fda581656b1b19c083d9b"}, + {url = "https://files.pythonhosted.org/packages/0a/84/68b848a373493b58615d6c10e9e8ccbaadfd540f84905421739a807704f8/websockets-11.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa5003845cdd21ac0dc6c9bf661c5beddd01116f6eb9eb3c8e272353d45b3288"}, + {url = "https://files.pythonhosted.org/packages/0f/d8/a997d3546aef9cc995a1126f7d7ade96c0e16c1a0efb9d2d430aee57c925/websockets-11.0.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:619d9f06372b3a42bc29d0cd0354c9bb9fb39c2cbc1a9c5025b4538738dbffaf"}, + {url = "https://files.pythonhosted.org/packages/14/fc/5cbbf439c925e1e184a0392ec477a30cee2fabc0e63807c1d4b6d570fb52/websockets-11.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b30c6590146e53149f04e85a6e4fcae068df4289e31e4aee1fdf56a0dead8f97"}, + {url = "https://files.pythonhosted.org/packages/16/49/ae616bd221efba84a3d78737b417f704af1ffa36f40dcaba5eb954dd4753/websockets-11.0.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e848f46a58b9fcf3d06061d17be388caf70ea5b8cc3466251963c8345e13f7eb"}, + {url = "https://files.pythonhosted.org/packages/19/d3/2ea3f95d83033675144b0848a0ae2e4998b3f763da09ec3df6bce97ea4e6/websockets-11.0.3-cp37-cp37m-win32.whl", hash = "sha256:e590228200fcfc7e9109509e4d9125eace2042fd52b595dd22bbc34bb282307f"}, + {url = "https://files.pythonhosted.org/packages/1b/3d/3dc77699fa4d003f2e810c321592f80f62b81d7b78483509de72ffe581fd/websockets-11.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0ee68fe502f9031f19d495dae2c268830df2760c0524cbac5d759921ba8c8e82"}, + {url = "https://files.pythonhosted.org/packages/20/62/5c6039c4069912adb27889ddd000403a2de9e0fe6aebe439b4e6b128a6b8/websockets-11.0.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e6316827e3e79b7b8e7d8e3b08f4e331af91a48e794d5d8b099928b6f0b85f20"}, + {url = "https://files.pythonhosted.org/packages/25/25/48540419005d07ed2d368a7eafb44ed4f33a2691ae4c210850bf31123c4a/websockets-11.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:03aae4edc0b1c68498f41a6772d80ac7c1e33c06c6ffa2ac1c27a07653e79d6f"}, + {url = "https://files.pythonhosted.org/packages/27/e9/605b0618d0864e9be7c2a78f22bff57aba9cf56b9fccde3205db9023ae22/websockets-11.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:92b2065d642bf8c0a82d59e59053dd2fdde64d4ed44efe4870fa816c1232647b"}, + {url = "https://files.pythonhosted.org/packages/30/a5/d641f2a9a4b4079cfddbb0726fc1b914be76a610aaedb45e4760899a4ce1/websockets-11.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bee9fcb41db2a23bed96c6b6ead6489702c12334ea20a297aa095ce6d31370d0"}, + {url = "https://files.pythonhosted.org/packages/32/2c/ab8ea64e9a7d8bf62a7ea7a037fb8d328d8bd46dbfe083787a9d452a148e/websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b67c6f5e5a401fc56394f191f00f9b3811fe843ee93f4a70df3c389d1adf857d"}, + {url = "https://files.pythonhosted.org/packages/36/19/0da435afb26a6c47c0c045a82e414912aa2ac10de5721276a342bd9fdfee/websockets-11.0.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:332d126167ddddec94597c2365537baf9ff62dfcc9db4266f263d455f2f031cb"}, + {url = "https://files.pythonhosted.org/packages/38/30/01a10fbf4cc1e7ffa07be9b0401501918fc9433d71fb7da4cfcef3bd26ca/websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8531fdcad636d82c517b26a448dcfe62f720e1922b33c81ce695d0edb91eb931"}, + {url = "https://files.pythonhosted.org/packages/38/ed/b8b133416536b6816e480594864e5950051db522714623eefc9e5275ec04/websockets-11.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6505c1b31274723ccaf5f515c1824a4ad2f0d191cec942666b3d0f3aa4cb4007"}, + {url = "https://files.pythonhosted.org/packages/44/a8/66c3a66b70b01a6c55fde486298766177fa11dd0d3a2c1cfc6820f25b4dc/websockets-11.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2529338a6ff0eb0b50c7be33dc3d0e456381157a31eefc561771ee431134a97f"}, + {url = "https://files.pythonhosted.org/packages/47/96/9d5749106ff57629b54360664ae7eb9afd8302fad1680ead385383e33746/websockets-11.0.3-py3-none-any.whl", hash = "sha256:6681ba9e7f8f3b19440921e99efbb40fc89f26cd71bf539e45d8c8a25c976dc6"}, + {url = "https://files.pythonhosted.org/packages/58/05/2efb520317340ece74bfc4d88e8f011dd71a4e6c263000bfffb71a343685/websockets-11.0.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1a99a7a71631f0efe727c10edfba09ea6bee4166a6f9c19aafb6c0b5917d09c"}, + {url = "https://files.pythonhosted.org/packages/58/0a/7570e15661a0a546c3a1152d95fe8c05480459bab36247f0acbf41f01a41/websockets-11.0.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bceab846bac555aff6427d060f2fcfff71042dba6f5fca7dc4f75cac815e57ca"}, + {url = "https://files.pythonhosted.org/packages/58/68/9403771de1b1c21a2e878e4841815af8c9f8893b094654934e2a5ee4dbc8/websockets-11.0.3-cp38-cp38-win32.whl", hash = "sha256:f61bdb1df43dc9c131791fbc2355535f9024b9a04398d3bd0684fc16ab07df74"}, + {url = "https://files.pythonhosted.org/packages/66/89/799f595c67b97a8a17e13d2764e088f631616bd95668aaa4c04b7cada136/websockets-11.0.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df41b9bc27c2c25b486bae7cf42fccdc52ff181c8c387bfd026624a491c2671b"}, + {url = "https://files.pythonhosted.org/packages/6b/ca/65d6986665888494eca4d5435a9741c822022996f0f4200c57ce4b9242f7/websockets-11.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:660e2d9068d2bedc0912af508f30bbeb505bbbf9774d98def45f68278cea20d3"}, + {url = "https://files.pythonhosted.org/packages/70/fc/71377f36ef3049f3bc7db7c0f3a7696929d5f836d7a18777131d994192a9/websockets-11.0.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34fd59a4ac42dff6d4681d8843217137f6bc85ed29722f2f7222bd619d15e95b"}, + {url = "https://files.pythonhosted.org/packages/72/89/0d150939f2e592ed78c071d69237ac1c872462cc62a750c5f592f3d4ab18/websockets-11.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:97b52894d948d2f6ea480171a27122d77af14ced35f62e5c892ca2fae9344311"}, + {url = "https://files.pythonhosted.org/packages/78/b2/df5452031b02b857851139806308f2af7c749069e25bfe15f2d559ade6e7/websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ac56b661e60edd453585f4bd68eb6a29ae25b5184fd5ba51e97652580458998"}, + {url = "https://files.pythonhosted.org/packages/82/3c/00f051abcf88aec5e952a8840076749b0b26a30c219dcae8ba70200998aa/websockets-11.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:86d2a77fd490ae3ff6fae1c6ceaecad063d3cc2320b44377efdde79880e11526"}, + {url = "https://files.pythonhosted.org/packages/89/8f/707a05d5725f956c78d252a5fd73b89fa3ac57dd3959381c2d1acb41cb13/websockets-11.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:01f5567d9cf6f502d655151645d4e8b72b453413d3819d2b6f1185abc23e82dd"}, + {url = "https://files.pythonhosted.org/packages/8a/77/a04d2911f6e2b9e781ce7ffc1e8516b54b85f985369eec8c853fd619d8e8/websockets-11.0.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:69269f3a0b472e91125b503d3c0b3566bda26da0a3261c49f0027eb6075086d1"}, + {url = "https://files.pythonhosted.org/packages/8a/bd/a5e5973899d78d44a540f50a9e30b01c6771e8bf7883204ee762060cf95a/websockets-11.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4841ed00f1026dfbced6fca7d963c4e7043aa832648671b5138008dc5a8f6d99"}, + {url = "https://files.pythonhosted.org/packages/8b/97/34178f5f7c29e679372d597cebfeff2aa45991d741d938117d4616e81a74/websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d5023a4b6a5b183dc838808087033ec5df77580485fc533e7dab2567851b0a4"}, + {url = "https://files.pythonhosted.org/packages/8c/a8/e81533499f84ef6cdd95d11d5b05fa827c0f097925afd86f16e6a2631d8e/websockets-11.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b58cbf0697721120866820b89f93659abc31c1e876bf20d0b3d03cef14faf84d"}, + {url = "https://files.pythonhosted.org/packages/8f/7b/4d4ecd29be7d08486e38f987a6603c491296d1e33fe55127d79aebb0333e/websockets-11.0.3-cp310-cp310-win32.whl", hash = "sha256:2d903ad4419f5b472de90cd2d40384573b25da71e33519a67797de17ef849b69"}, + {url = "https://files.pythonhosted.org/packages/8f/f2/8a3eb016be19743c7eb9e67c855df0fdfa5912534ffaf83a05b62667d761/websockets-11.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8c82f11964f010053e13daafdc7154ce7385ecc538989a354ccc7067fd7028fd"}, + {url = "https://files.pythonhosted.org/packages/94/8c/266155c14b7a26deca6fa4c4d5fd15b0ab32725d78a2acfcf6b24943585d/websockets-11.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:b16fff62b45eccb9c7abb18e60e7e446998093cdcb50fed33134b9b6878836de"}, + {url = "https://files.pythonhosted.org/packages/98/a7/0ed69892981351e5acf88fac0ff4c801fabca2c3bdef9fca4c7d3fde8c53/websockets-11.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:1d2256283fa4b7f4c7d7d3e84dc2ece74d341bce57d5b9bf385df109c2a1a82f"}, + {url = "https://files.pythonhosted.org/packages/99/23/43071c989c0f87f612e7bccee98d00b04bddd3aca0cdc1ffaf31f6f8a4b4/websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c114e8da9b475739dde229fd3bc6b05a6537a88a578358bc8eb29b4030fac9c9"}, + {url = "https://files.pythonhosted.org/packages/9a/6e/0fd7274042f46acb589161407f4b505b44c68d369437ce919bae1fa9b8c4/websockets-11.0.3-cp39-cp39-win32.whl", hash = "sha256:c7f3cb904cce8e1be667c7e6fef4516b98d1a6a0635a58a57528d577ac18a128"}, + {url = "https://files.pythonhosted.org/packages/a0/1a/3da73e69ebc00649d11ed836541c92c1a2df0b8a8aa641a2c8746e7c2b9c/websockets-11.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3580dd9c1ad0701169e4d6fc41e878ffe05e6bdcaf3c412f9d559389d0c9e016"}, + {url = "https://files.pythonhosted.org/packages/a0/39/acc3d4b15c5207ef7cca823c37eca8c74e3e1a1a63a397798986be3bdef7/websockets-11.0.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68b977f21ce443d6d378dbd5ca38621755f2063d6fdb3335bda981d552cfff86"}, + {url = "https://files.pythonhosted.org/packages/a6/1b/5c83c40f8d3efaf0bb2fdf05af94fb920f74842b7aaf31d7598e3ee44d58/websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcacf2c7a6c3a84e720d1bb2b543c675bf6c40e460300b628bab1b1efc7c034c"}, + {url = "https://files.pythonhosted.org/packages/a6/9c/2356ecb952fd3992b73f7a897d65e57d784a69b94bb8d8fd5f97531e5c02/websockets-11.0.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:279e5de4671e79a9ac877427f4ac4ce93751b8823f276b681d04b2156713b9dd"}, + {url = "https://files.pythonhosted.org/packages/a7/8c/7100e9cf310fe1d83d1ae1322203f4eb2b767a7c2b301c1e70db6270306f/websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de36fe9c02995c7e6ae6efe2e205816f5f00c22fd1fbf343d4d18c3d5ceac2f5"}, + {url = "https://files.pythonhosted.org/packages/a7/f7/1e852351e8073c32885172a6bef64c95d14c13ff3634b01d4a1086321491/websockets-11.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9d9acd80072abcc98bd2c86c3c9cd4ac2347b5a5a0cae7ed5c0ee5675f86d9af"}, + {url = "https://files.pythonhosted.org/packages/a9/5e/b25c60067d700e811dccb4e3c318eeadd3a19d8b3620de9f97434af777a7/websockets-11.0.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:def07915168ac8f7853812cc593c71185a16216e9e4fa886358a17ed0fd9fcf6"}, + {url = "https://files.pythonhosted.org/packages/b2/ec/56bdd12d847e4fc2d0a7ba2d7f1476f79cda50599d11ffb6080b86f21ef1/websockets-11.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffd7dcaf744f25f82190856bc26ed81721508fc5cbf2a330751e135ff1283564"}, + {url = "https://files.pythonhosted.org/packages/b5/94/ac47552208583d5dbcce468430c1eb2ae18962f6b3a694a2b7727cc60d4a/websockets-11.0.3-cp311-cp311-win32.whl", hash = "sha256:e1459677e5d12be8bbc7584c35b992eea142911a6236a3278b9b5ce3326f282c"}, + {url = "https://files.pythonhosted.org/packages/b5/a8/8900184ab0b06b6e620ba7e92cf2faa5caa9ba86e148541b8fff1c7b6646/websockets-11.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9f59a3c656fef341a99e3d63189852be7084c0e54b75734cde571182c087b152"}, + {url = "https://files.pythonhosted.org/packages/b6/96/0d586c25d043aeab9457dad8e407251e3baf314d871215f91847e7b995c4/websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e063b1865974611313a3849d43f2c3f5368093691349cf3c7c8f8f75ad7cb280"}, + {url = "https://files.pythonhosted.org/packages/b9/6b/26b28115b46e23e74ede76d95792eedfe8c58b21f4daabfff1e9f159c8fe/websockets-11.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d67ac60a307f760c6e65dad586f556dde58e683fab03323221a4e530ead6f74d"}, + {url = "https://files.pythonhosted.org/packages/ba/6c/5c0322b2875e8395e6bf0eff11f43f3e25da7ef5b12f4d908cd3a19ea841/websockets-11.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:54c6e5b3d3a8936a4ab6870d46bdd6ec500ad62bde9e44462c32d18f1e9a8e54"}, + {url = "https://files.pythonhosted.org/packages/c0/21/cb9dfbbea8dc0ad89ced52630e7e61edb425fb9fdc6002f8d0c5dd26b94b/websockets-11.0.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:777354ee16f02f643a4c7f2b3eff8027a33c9861edc691a2003531f5da4f6bc8"}, + {url = "https://files.pythonhosted.org/packages/c0/a8/a8a582ebeeecc8b5f332997d44c57e241748f8a9856e06a38a5a13b30796/websockets-11.0.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1f0524f203e3bd35149f12157438f406eff2e4fb30f71221c8a5eceb3617b6b"}, + {url = "https://files.pythonhosted.org/packages/c4/5b/60eccd7e9703bbe93fc4167d1e7ada7e8e8e51544122198d63fd8e3460b7/websockets-11.0.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fb06eea71a00a7af0ae6aefbb932fb8a7df3cb390cc217d51a9ad7343de1b8d0"}, + {url = "https://files.pythonhosted.org/packages/c4/f5/15998b164c183af0513bba744b51ecb08d396ff86c0db3b55d62624d1f15/websockets-11.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1fdf26fa8a6a592f8f9235285b8affa72748dc12e964a5518c6c5e8f916716f7"}, + {url = "https://files.pythonhosted.org/packages/c6/91/f36454b87edf10a95be9c7212d2dcb8c606ddbf7a183afdc498933acdd19/websockets-11.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1553cb82942b2a74dd9b15a018dce645d4e68674de2ca31ff13ebc2d9f283788"}, + {url = "https://files.pythonhosted.org/packages/c9/fb/ae5ed4be3514287cf8f6c348c87e1392a6e3f4d6eadae75c18847a2f84b6/websockets-11.0.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7622a89d696fc87af8e8d280d9b421db5133ef5b29d3f7a1ce9f1a7bf7fcfa11"}, + {url = "https://files.pythonhosted.org/packages/ca/20/25211be61d50189650fb0ec6084b6d6339f5c7c6436a6c217608dcb553e4/websockets-11.0.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4b253869ea05a5a073ebfdcb5cb3b0266a57c3764cf6fe114e4cd90f4bfa5f5e"}, + {url = "https://files.pythonhosted.org/packages/d1/ec/7e2b9bebc2e9b4a48404144106bbc6a7ace781feeb0e6a3829551e725fa5/websockets-11.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8a34e13a62a59c871064dfd8ffb150867e54291e46d4a7cf11d02c94a5275bae"}, + {url = "https://files.pythonhosted.org/packages/d8/3b/2ed38e52eed4cf277f9df5f0463a99199a04d9e29c9e227cfafa57bd3993/websockets-11.0.3.tar.gz", hash = "sha256:88fc51d9a26b10fc331be344f1781224a375b78488fc343620184e95a4b27016"}, + {url = "https://files.pythonhosted.org/packages/d9/36/5741e62ccf629c8e38cc20f930491f8a33ce7dba972cae93dba3d6f02552/websockets-11.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f1a3f10f836fab6ca6efa97bb952300b20ae56b409414ca85bff2ad241d2a61"}, + {url = "https://files.pythonhosted.org/packages/de/0e/d7274e4d41d7b34f204744c27a23707be2ecefaf6f7df2145655f086ecd7/websockets-11.0.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:41f696ba95cd92dc047e46b41b26dd24518384749ed0d99bea0a941ca87404c4"}, + {url = "https://files.pythonhosted.org/packages/e1/76/88640f8aeac7eb0d058b913e7bb72682f8d569db44c7d30e576ec4777ce1/websockets-11.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3ccc8a0c387629aec40f2fc9fdcb4b9d5431954f934da3eaf16cdc94f67dbfac"}, + {url = "https://files.pythonhosted.org/packages/e1/7c/0ad6e7ef0a054d73092f616d20d3d9bd3e1b837554cb20a52d8dd9f5b049/websockets-11.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:e7837cb169eca3b3ae94cc5787c4fed99eef74c0ab9506756eea335e0d6f3ed8"}, + {url = "https://files.pythonhosted.org/packages/e2/2f/3ad8ac4a9dc9d685e098e534180a36ed68fe2e85e82e225e00daec86bb94/websockets-11.0.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f2e58f2c36cc52d41f2659e4c0cbf7353e28c8c9e63e30d8c6d3494dc9fdedcf"}, + {url = "https://files.pythonhosted.org/packages/e9/26/1dfaa81788f61c485b4d65f1b28a19615e39f9c45100dce5e2cbf5ad1352/websockets-11.0.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f467ba0050b7de85016b43f5a22b46383ef004c4f672148a8abf32bc999a87f0"}, + {url = "https://files.pythonhosted.org/packages/eb/fb/2af7fc3ce2c3f1378d48a15802b4ff2caf6c0dfac13291e73c557caf04f7/websockets-11.0.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:42cc5452a54a8e46a032521d7365da775823e21bfba2895fb7b77633cce031bb"}, + {url = "https://files.pythonhosted.org/packages/ec/3f/0c5cae14e9e86401105833383405787ae4caddd476a8fc5561259253dab7/websockets-11.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a073fc9ab1c8aff37c99f11f1641e16da517770e31a37265d2755282a5d28aa"}, + {url = "https://files.pythonhosted.org/packages/ed/45/466944e00b324ae3a1fddb305b4abf641f582e131548f07bcd970971b154/websockets-11.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ed058398f55163a79bb9f06a90ef9ccc063b204bb346c4de78efc5d15abfe602"}, + {url = "https://files.pythonhosted.org/packages/f3/82/2d1f3395d47fab65fa8b801e2251b324300ed8db54753b6fb7919cef0c11/websockets-11.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84d27a4832cc1a0ee07cdcf2b0629a8a72db73f4cf6de6f0904f6661227f256f"}, + {url = "https://files.pythonhosted.org/packages/f4/3f/65dfa50084a06ab0a05f3ca74195c2c17a1c075b8361327d831ccce0a483/websockets-11.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c792ea4eabc0159535608fc5658a74d1a81020eb35195dd63214dcf07556f67e"}, +] +"wheel 0.41.1" = [ + {url = "https://files.pythonhosted.org/packages/28/f5/6955d7b3a5d71ce6bac104f9cf98c1b0513ad656cdaca8ea7d579196f771/wheel-0.41.1-py3-none-any.whl", hash = "sha256:473219bd4cbedc62cea0cb309089b593e47c15c4a2531015f94e4e3b9a0f6981"}, + {url = "https://files.pythonhosted.org/packages/c9/3d/02a14af2b413d7abf856083f327744d286f4468365cddace393a43d9d540/wheel-0.41.1.tar.gz", hash = "sha256:12b911f083e876e10c595779709f8a88a59f45aacc646492a67fe9ef796c1b47"}, +] +"wrapt 1.15.0" = [ + {url = "https://files.pythonhosted.org/packages/0c/6e/f80c23efc625c10460240e31dcb18dd2b34b8df417bc98521fbfd5bc2e9a/wrapt-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923"}, + {url = "https://files.pythonhosted.org/packages/0f/9a/179018bb3f20071f39597cd38fc65d6285d7b89d57f6c51f502048ed28d9/wrapt-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d"}, + {url = "https://files.pythonhosted.org/packages/12/5a/fae60a8bc9b07a3a156989b79e14c58af05ab18375749ee7c12b2f0dddbd/wrapt-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1"}, + {url = "https://files.pythonhosted.org/packages/18/f6/659d7c431a57da9c9a86945834ab2bf512f1d9ebefacea49135a0135ef1a/wrapt-1.15.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034"}, + {url = "https://files.pythonhosted.org/packages/1e/3c/cb96dbcafbf3a27413fb15e0a1997c4610283f895dc01aca955cd2fda8b9/wrapt-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c"}, + {url = "https://files.pythonhosted.org/packages/20/01/baec2650208284603961d61f53ee6ae8e3eff63489c7230dff899376a6f6/wrapt-1.15.0-cp35-cp35m-win_amd64.whl", hash = "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"}, + {url = "https://files.pythonhosted.org/packages/21/42/36c98e9c024978f52c218f22eba1addd199a356ab16548af143d3a72ac0d/wrapt-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09"}, + {url = "https://files.pythonhosted.org/packages/23/0a/9964d7141b8c5e31c32425d3412662a7873aaf0c0964166f4b37b7db51b6/wrapt-1.15.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3"}, + {url = "https://files.pythonhosted.org/packages/29/41/f05bf85417473cf6fe4eec7396c63762e5a457a42102bd1b8af059af6586/wrapt-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd"}, + {url = "https://files.pythonhosted.org/packages/2b/fb/c31489631bb94ac225677c1090f787a4ae367614b5277f13dbfde24b2b69/wrapt-1.15.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975"}, + {url = "https://files.pythonhosted.org/packages/2d/47/16303c59a890696e1a6fd82ba055fc4e0f793fb4815b5003f1f85f7202ce/wrapt-1.15.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7"}, + {url = "https://files.pythonhosted.org/packages/2e/ce/90dcde9ff9238689f111f07b46da2db570252445a781ea147ff668f651b0/wrapt-1.15.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2"}, + {url = "https://files.pythonhosted.org/packages/31/e6/6ac59c5570a7b9aaecb10de39f70dacd0290620330277e60b29edcf8bc9a/wrapt-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2"}, + {url = "https://files.pythonhosted.org/packages/39/ee/2b8d608f2bcf86242daadf5b0b746c11d3657b09892345f10f171b5ca3ac/wrapt-1.15.0-cp35-cp35m-win32.whl", hash = "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559"}, + {url = "https://files.pythonhosted.org/packages/44/a1/40379212a0b678f995fdb4f4f28aeae5724f3212cdfbf97bee8e6fba3f1b/wrapt-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420"}, + {url = "https://files.pythonhosted.org/packages/45/90/a959fa50084d7acc2e628f093c9c2679dd25085aa5085a22592e028b3e06/wrapt-1.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46"}, + {url = "https://files.pythonhosted.org/packages/47/dd/bee4d33058656c0b2e045530224fcddd9492c354af5d20499e5261148dcb/wrapt-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317"}, + {url = "https://files.pythonhosted.org/packages/48/65/0061e7432ca4b635e96e60e27e03a60ddaca3aeccc30e7415fed0325c3c2/wrapt-1.15.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec"}, + {url = "https://files.pythonhosted.org/packages/4a/7b/c63103817bd2f3b0145608ef642ce90d8b6d1e5780d218bce92e93045e06/wrapt-1.15.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653"}, + {url = "https://files.pythonhosted.org/packages/50/eb/af864a01300878f69b4949f8381ad57d5519c1791307e9fd0bc7f5ab50a5/wrapt-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29"}, + {url = "https://files.pythonhosted.org/packages/54/21/282abeb456f22d93533b2d373eeb393298a30b0cb0683fa8a4ed77654273/wrapt-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1"}, + {url = "https://files.pythonhosted.org/packages/55/20/90f5affc2c879db408124ce14b9443b504f961e47a517dff4f24a00df439/wrapt-1.15.0-cp38-cp38-win32.whl", hash = "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b"}, + {url = "https://files.pythonhosted.org/packages/5d/c4/3cc25541ec0404dd1d178e7697a34814d77be1e489cd6f8cb055ac688314/wrapt-1.15.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98"}, + {url = "https://files.pythonhosted.org/packages/65/be/3ae5afe9d78d97595b28914fa7e375ebc6329549d98f02768d5a08f34937/wrapt-1.15.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e"}, + {url = "https://files.pythonhosted.org/packages/6b/b0/bde5400fdf6d18cb7ef527831de0f86ac206c4da1670b67633e5a547b05f/wrapt-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72"}, + {url = "https://files.pythonhosted.org/packages/78/f2/106d90140a93690eab240fae76759d62dae639fcec1bd098eccdb83aa38f/wrapt-1.15.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a"}, + {url = "https://files.pythonhosted.org/packages/7f/b6/6dc0ddacd20337b4ce6ab0d6b0edc7da3898f85c4f97df7f30267e57509e/wrapt-1.15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0"}, + {url = "https://files.pythonhosted.org/packages/81/1e/0bb8f01c6ac5baba66ef1ab65f4644bede856c3c7aede18c896be222151c/wrapt-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba"}, + {url = "https://files.pythonhosted.org/packages/88/f1/4dfaa1ad111d2a48429dca133e46249922ee2f279e9fdd4ab5b149cd6c71/wrapt-1.15.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90"}, + {url = "https://files.pythonhosted.org/packages/8a/1c/740c3ad1b7754dd7213f4df09ccdaf6b19e36da5ff3a269444ba9e103f1b/wrapt-1.15.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9"}, + {url = "https://files.pythonhosted.org/packages/8f/87/ba6dc86e8edb28fd1e314446301802751bd3157e9780385c9eef633994b9/wrapt-1.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e"}, + {url = "https://files.pythonhosted.org/packages/94/55/91dd3a7efbc1db2b07bbfc490d48e8484852c355d55e61e8b1565d7725f6/wrapt-1.15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7"}, + {url = "https://files.pythonhosted.org/packages/96/37/a33c1220e8a298ab18eb070b6a59e4ccc3f7344b434a7ac4bd5d4bdccc97/wrapt-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee"}, + {url = "https://files.pythonhosted.org/packages/9b/50/383c155a05e3e0361d209e3f55ec823f3736c7a46b29923ea33ab85e8d70/wrapt-1.15.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29"}, + {url = "https://files.pythonhosted.org/packages/9d/40/fee1288d654c80fe1bc5ecee1c8d58f761a39bb30c73f1ce106701dd8b0a/wrapt-1.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e"}, + {url = "https://files.pythonhosted.org/packages/a2/3e/ee671ac60945154dfa3a406b8cb5cef2e3b4fa31c7d04edeb92716342026/wrapt-1.15.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0"}, + {url = "https://files.pythonhosted.org/packages/a4/af/8552671e4e7674fcae14bd3976dd9dc6a2b7294730e4a9a94597ac292a21/wrapt-1.15.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248"}, + {url = "https://files.pythonhosted.org/packages/a6/32/f4868adc994648fac4cfe347bcc1381c9afcb1602c8ba0910f36b96c5449/wrapt-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e"}, + {url = "https://files.pythonhosted.org/packages/a7/da/04883b14284c437eac98c7ad2959197f02acbabd57d5ea8ff4893a7c3920/wrapt-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0"}, + {url = "https://files.pythonhosted.org/packages/a9/64/886e512f438f12424b48a3ab23ae2583ec633be6e13eb97b0ccdff8e328a/wrapt-1.15.0-cp310-cp310-win32.whl", hash = "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1"}, + {url = "https://files.pythonhosted.org/packages/aa/24/bbd64ee4e1db9c75ec2a9677c538866f81800bcd2a8abd1a383369369cf5/wrapt-1.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c"}, + {url = "https://files.pythonhosted.org/packages/af/23/cf5dbfd676480fa8fc6eecc4c413183cd8e14369321c5111fec5c12550e9/wrapt-1.15.0-cp39-cp39-win32.whl", hash = "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff"}, + {url = "https://files.pythonhosted.org/packages/af/7f/25913aacbe0c2c68e7354222bdefe4e840489725eb835e311c581396f91f/wrapt-1.15.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f"}, + {url = "https://files.pythonhosted.org/packages/b1/8b/f4c02cf1f841dede987f93c37d42256dc4a82cd07173ad8a5458eee1c412/wrapt-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e"}, + {url = "https://files.pythonhosted.org/packages/b2/b0/a56b129822568d9946e009e8efd53439b9dd38cc1c4af085aa44b2485b40/wrapt-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1"}, + {url = "https://files.pythonhosted.org/packages/b6/0c/435198dbe6961c2343ca725be26b99c8aee615e32c45bc1cb2a960b06183/wrapt-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364"}, + {url = "https://files.pythonhosted.org/packages/b7/3d/9d3cd75f7fc283b6e627c9b0e904189c41ca144185fd8113a1a094dec8ca/wrapt-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86"}, + {url = "https://files.pythonhosted.org/packages/b9/40/975fbb1ab03fa987900bacc365645c4cbead22baddd273b4f5db7f9843d2/wrapt-1.15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c"}, + {url = "https://files.pythonhosted.org/packages/bd/47/57ffe222af59fae1eb56bca7d458b704a9b59380c47f0921efb94dc4786a/wrapt-1.15.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6"}, + {url = "https://files.pythonhosted.org/packages/c3/12/5fabf0014a0f30eb3975b7199ac2731215a40bc8273083f6a89bd6cadec6/wrapt-1.15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc"}, + {url = "https://files.pythonhosted.org/packages/c4/e3/01f879f8e7c1221c72dbd4bfa106624ee3d01cb8cbc82ef57fbb95880cac/wrapt-1.15.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb"}, + {url = "https://files.pythonhosted.org/packages/c7/cd/18d95465323f29e3f3fd3ff84f7acb402a6a61e6caf994dced7140d78f85/wrapt-1.15.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd"}, + {url = "https://files.pythonhosted.org/packages/ca/1c/5caf61431705b3076ca1152abfd6da6304697d7d4fe48bb3448a6decab40/wrapt-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb"}, + {url = "https://files.pythonhosted.org/packages/cd/a0/84b8fe24af8d7f7374d15e0da1cd5502fff59964bbbf34982df0ca2c9047/wrapt-1.15.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f"}, + {url = "https://files.pythonhosted.org/packages/cd/f0/060add4fcb035024f84fb3b5523fb2b119ac08608af3f61dbdda38477900/wrapt-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6"}, + {url = "https://files.pythonhosted.org/packages/cf/b1/3c24fc0f6b589ad8c99cfd1cd3e586ef144e16aaf9381ed952d047a7ee54/wrapt-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475"}, + {url = "https://files.pythonhosted.org/packages/d1/74/3c99ce16947f7af901f6203ab4a3d0908c4db06e800571dabfe8525fa925/wrapt-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752"}, + {url = "https://files.pythonhosted.org/packages/d2/60/9fe25f4cd910ae94e75a1fd4772b058545e107a82629a5ca0f2cd7cc34d5/wrapt-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e"}, + {url = "https://files.pythonhosted.org/packages/d7/4b/1bd4837362d31d402b9bc1a27cdd405baf994dbf9942696f291d2f7eeb73/wrapt-1.15.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8"}, + {url = "https://files.pythonhosted.org/packages/dd/42/9eedee19435dfc0478cdb8bdc71800aab15a297d1074f1aae0d9489adbc3/wrapt-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705"}, + {url = "https://files.pythonhosted.org/packages/dd/e9/85e780a6b70191114b13b129867cec2fab84279f6beb788e130a26e4ca58/wrapt-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079"}, + {url = "https://files.pythonhosted.org/packages/dd/eb/389f9975a6be31ddd19d29128a11f1288d07b624e464598a4b450f8d007e/wrapt-1.15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29"}, + {url = "https://files.pythonhosted.org/packages/de/77/e2ebfa2f46c19094888a364fdb59aeab9d3336a3ad7ccdf542de572d2a35/wrapt-1.15.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094"}, + {url = "https://files.pythonhosted.org/packages/e8/86/fc38e58843159bdda745258d872b1187ad916087369ec57ef93f5e832fa8/wrapt-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7"}, + {url = "https://files.pythonhosted.org/packages/ec/f4/f84538a367105f0a7e507f0c6766d3b15b848fd753647bbf0c206399b322/wrapt-1.15.0-cp311-cp311-win32.whl", hash = "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416"}, + {url = "https://files.pythonhosted.org/packages/ee/25/83f5dcd9f96606521da2d0e7a03a18800264eafb59b569ff109c4d2fea67/wrapt-1.15.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92"}, + {url = "https://files.pythonhosted.org/packages/f6/89/bf77b063c594795aaa056cac7b467463702f346d124d46d7f06e76e8cd97/wrapt-1.15.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8"}, + {url = "https://files.pythonhosted.org/packages/f6/d3/3c6bd4db883537c40eb9d41d738d329d983d049904f708267f3828a60048/wrapt-1.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a"}, + {url = "https://files.pythonhosted.org/packages/f8/49/10013abe31f6892ae57c5cc260f71b7e08f1cc00f0d7b2bcfa482ea74349/wrapt-1.15.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b"}, + {url = "https://files.pythonhosted.org/packages/f8/7d/73e4e3cdb2c780e13f9d87dc10488d7566d8fd77f8d68f0e416bfbd144c7/wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"}, + {url = "https://files.pythonhosted.org/packages/f8/f8/e068dafbb844c1447c55b23c921f3d338cddaba4ea53187a7dd0058452d9/wrapt-1.15.0-py3-none-any.whl", hash = "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640"}, + {url = "https://files.pythonhosted.org/packages/fb/2d/b6fd53b7dbf94d542866cbf1021b9a62595177fc8405fd75e0a5bf3fa3b8/wrapt-1.15.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418"}, + {url = "https://files.pythonhosted.org/packages/fb/bd/ca7fd05a45e7022f3b780a709bbdb081a6138d828ecdb5b7df113a3ad3be/wrapt-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727"}, + {url = "https://files.pythonhosted.org/packages/fd/8a/db55250ad0b536901173d737781e3b5a7cc7063c46b232c2e3a82a33c032/wrapt-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145"}, + {url = "https://files.pythonhosted.org/packages/ff/f6/c044dec6bec4ce64fbc92614c5238dd432780b06293d2efbcab1a349629c/wrapt-1.15.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019"}, +] +"yarl 1.9.2" = [ + {url = "https://files.pythonhosted.org/packages/0e/b1/a65fcf0363ae8c08c0e586772a34cc15b4200bae163eed24258cc95cda90/yarl-1.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:566185e8ebc0898b11f8026447eacd02e46226716229cea8db37496c8cdd26e0"}, + {url = "https://files.pythonhosted.org/packages/10/b1/13887c4fbf885d64f4b8c1bac403338f7c1c07a34e63d767af8d0972c28d/yarl-1.9.2-cp37-cp37m-win32.whl", hash = "sha256:f364d3480bffd3aa566e886587eaca7c8c04d74f6e8933f3f2c996b7f09bee1b"}, + {url = "https://files.pythonhosted.org/packages/11/3d/785761e64dc90fda6feb9bd0459dc55ebe282a7d4564642a4a8ee277e0c0/yarl-1.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a74dcbfe780e62f4b5a062714576f16c2f3493a0394e555ab141bf0d746bb955"}, + {url = "https://files.pythonhosted.org/packages/12/27/efe7476bdadb2564d7775e0895df56f3e3adf39495094750fb319599180e/yarl-1.9.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13414591ff516e04fcdee8dc051c13fd3db13b673c7a4cb1350e6b2ad9639ad3"}, + {url = "https://files.pythonhosted.org/packages/12/c0/18265b85980450b75641a32dd12635485241e295310c1b04e04ac0cc634e/yarl-1.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38a3928ae37558bc1b559f67410df446d1fbfa87318b124bf5032c31e3447b74"}, + {url = "https://files.pythonhosted.org/packages/14/5b/a0bf4a601ddf4073ae0dcd66a90708aaa944a4ad0addf777d9f1fcd6f4f0/yarl-1.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3b078dbe227f79be488ffcfc7a9edb3409d018e0952cf13f15fd6512847f3f7"}, + {url = "https://files.pythonhosted.org/packages/15/5a/5435fe438874f03aa9f559c5173418fbac680b095ac394e88b0825d12ebd/yarl-1.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56ff08ab5df8429901ebdc5d15941b59f6253393cb5da07b4170beefcf1b2528"}, + {url = "https://files.pythonhosted.org/packages/16/dd/3ef5a4c74f9516f2193b0782046802d73c5475ef49678473a608194f3bf1/yarl-1.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2c315df3293cd521033533d242d15eab26583360b58f7ee5d9565f15fee1bef4"}, + {url = "https://files.pythonhosted.org/packages/19/41/97678e848ce963cd3e89c4dcc13900c9afedd42e5c7d9cfb019716f8bb2a/yarl-1.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2b0738fb871812722a0ac2154be1f049c6223b9f6f22eec352996b69775b36d4"}, + {url = "https://files.pythonhosted.org/packages/19/ed/deeec0a15bf1d9a3d2d2102ebb9dbd84dc312a00fbf88564b56b05f266a1/yarl-1.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82aa6264b36c50acfb2424ad5ca537a2060ab6de158a5bd2a72a032cc75b9eb8"}, + {url = "https://files.pythonhosted.org/packages/1d/78/a273c991086df02837676dc68ccf50d56b2fe624d75258d521c651a65d82/yarl-1.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d4e2c6d555e77b37288eaf45b8f60f0737c9efa3452c6c44626a5455aeb250b9"}, + {url = "https://files.pythonhosted.org/packages/25/68/b67d964bc7768f6462b51c05dd879b6c6f6e55168086b948e6e3e6f6928e/yarl-1.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:9b3152f2f5677b997ae6c804b73da05a39daa6a9e85a512e0e6823d81cdad7cc"}, + {url = "https://files.pythonhosted.org/packages/2d/76/d9178fe8fe5823370b26bbd1bbb159c2cc3f7449cade1a50818bcfc98cae/yarl-1.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a96c19c52ff442a808c105901d0bdfd2e28575b3d5f82e2f5fd67e20dc5f4ea"}, + {url = "https://files.pythonhosted.org/packages/30/55/eda822473c6206470a89ca3550efa23202310a2e56317e55afb709008fd5/yarl-1.9.2-cp310-cp310-win32.whl", hash = "sha256:8ea48e0a2f931064469bdabca50c2f578b565fc446f302a79ba6cc0ee7f384d3"}, + {url = "https://files.pythonhosted.org/packages/31/2c/e6af0f7710412e4ed49c1641f04ed1af334d448d51c55150235e3381f0a7/yarl-1.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75df5ef94c3fdc393c6b19d80e6ef1ecc9ae2f4263c09cacb178d871c02a5ba9"}, + {url = "https://files.pythonhosted.org/packages/34/1f/9a915044ec1e13f046e6d023e4dd1ea43316add36e199d46237d7d97cc88/yarl-1.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b03917871bf859a81ccb180c9a2e6c1e04d2f6a51d953e6a5cdd70c93d4e5a2a"}, + {url = "https://files.pythonhosted.org/packages/35/0f/a68344daf90536755f4a890dbbab65dc6ca58c4a0268cf79bd7c5ddc1468/yarl-1.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ec53a0ea2a80c5cd1ab397925f94bff59222aa3cf9c6da938ce05c9ec20428d"}, + {url = "https://files.pythonhosted.org/packages/3b/b2/34e45989fa5fcf406dd471c517697a5bf483fb1bcaebcf2bedd2b86e0cbb/yarl-1.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:b8cc1863402472f16c600e3e93d542b7e7542a540f95c30afd472e8e549fc3f7"}, + {url = "https://files.pythonhosted.org/packages/3b/b4/d20b0444fa0f0e7efdb328d16efd44d03a02427e090d02f936b990c9e9bc/yarl-1.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59723a029760079b7d991a401386390c4be5bfec1e7dd83e25a6a0881859e716"}, + {url = "https://files.pythonhosted.org/packages/3d/ea/041c270d8853572eec3e6ad26fe7a53eb85d68c052bfbc79b42ae5e63f8a/yarl-1.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:149ddea5abf329752ea5051b61bd6c1d979e13fbf122d3a1f9f0c8be6cb6f63c"}, + {url = "https://files.pythonhosted.org/packages/40/2c/aa941cb37ed206f9f46158dcb6398f17a5bcb95124970fd43d017650b9b2/yarl-1.9.2-cp39-cp39-win32.whl", hash = "sha256:b124e2a6d223b65ba8768d5706d103280914d61f5cae3afbc50fc3dfcc016623"}, + {url = "https://files.pythonhosted.org/packages/45/c6/c58e25159d2186247272595ffff1aaba319d10aab20b40429a6e80418328/yarl-1.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:53338749febd28935d55b41bf0bcc79d634881195a39f6b2f767870b72514caf"}, + {url = "https://files.pythonhosted.org/packages/49/d7/3b21ce9742ded3e942bcf48b01ebe29fdcd8eb9dc3296ebfbb77660ee8bb/yarl-1.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf74d08542c3a9ea97bb8f343d4fcbd4d8f91bba5ec9d5d7f792dbe727f88938"}, + {url = "https://files.pythonhosted.org/packages/50/af/93f1b6d02e936d49e664a8eb4374877e5bacfef115c956939729ac9e2ca8/yarl-1.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:832b7e711027c114d79dffb92576acd1bd2decc467dec60e1cac96912602d0e6"}, + {url = "https://files.pythonhosted.org/packages/53/87/f5588bdc6eba3ca4521bd37094563e8442ba2cff3d6b7e5a2cab48fdc96d/yarl-1.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b25322201585c69abc7b0e89e72790469f7dad90d26754717f3310bfe30331c2"}, + {url = "https://files.pythonhosted.org/packages/54/90/8e7f57b0c83805652bd1c26663f9979d12fed8d22516c14c7f3021f97a19/yarl-1.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b9a4e67ad7b646cd6f0938c7ebfd60e481b7410f574c560e455e938d2da8e0f4"}, + {url = "https://files.pythonhosted.org/packages/58/c7/bad405a8b0b2366c3c21d650831d58fadca95af583c6dc1d2349512741cf/yarl-1.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:066c163aec9d3d073dc9ffe5dd3ad05069bcb03fcaab8d221290ba99f9f69ee3"}, + {url = "https://files.pythonhosted.org/packages/5a/3e/53e50f6b492d73dab887428acff54d4ea1be7575bee2d846b932dff459d9/yarl-1.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fcd436ea16fee7d4207c045b1e340020e58a2597301cfbcfdbe5abd2356c2fb"}, + {url = "https://files.pythonhosted.org/packages/5d/6f/06e3e0b1935e8d65e7c52238b5e82b2afb3e067ff69b07a66b909f3b2432/yarl-1.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:674ca19cbee4a82c9f54e0d1eee28116e63bc6fd1e96c43031d11cbab8b2afd5"}, + {url = "https://files.pythonhosted.org/packages/5f/3f/04b3c5e57844fb9c034b09c5cb6d2b43de5d64a093c30529fd233e16cf09/yarl-1.9.2.tar.gz", hash = "sha256:04ab9d4b9f587c06d801c2abfe9317b77cdf996c65a90d5e84ecc45010823571"}, + {url = "https://files.pythonhosted.org/packages/62/c8/b8e048ba98a0f41d46a22060a57f913b4f9ed9c4f6862de36b8137bb67e2/yarl-1.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:783185c75c12a017cc345015ea359cc801c3b29a2966c2655cd12b233bf5a2be"}, + {url = "https://files.pythonhosted.org/packages/63/80/95ae601d7b7f5f6b53174d91d94df865db9166895934d5065e924634dc76/yarl-1.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bf345c3a4f5ba7f766430f97f9cc1320786f19584acc7086491f45524a551ac"}, + {url = "https://files.pythonhosted.org/packages/6a/67/1ea83dd287358d47adc49f2aeb9e4e8ae72bec8ae2604c3bcae1e7fd73de/yarl-1.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0c77533b5ed4bcc38e943178ccae29b9bcf48ffd1063f5821192f23a1bd27b9"}, + {url = "https://files.pythonhosted.org/packages/6b/83/e0cb0cbb37098475fca29b8c5000fed417b67fc2c6dc8d0fa7e32c000c80/yarl-1.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:be6b3fdec5c62f2a67cb3f8c6dbf56bbf3f61c0f046f84645cd1ca73532ea051"}, + {url = "https://files.pythonhosted.org/packages/75/4d/b86c4fd2c689eaeb078de274f6822b02872a5e19557af16257be3eda20ee/yarl-1.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b7232f8dfbd225d57340e441d8caf8652a6acd06b389ea2d3222b8bc89cbfca6"}, + {url = "https://files.pythonhosted.org/packages/78/1d/0554e6d4c8669ca707e93f188111e29cf8a3c97cf2e8e8448ad3b284ae84/yarl-1.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3a53ba34a636a256d767c086ceb111358876e1fb6b50dfc4d3f4951d40133d5"}, + {url = "https://files.pythonhosted.org/packages/7a/ff/b490d9995b23e8e6d773679b8f3c8347defe39570f63f3eb391ad208d853/yarl-1.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6a5883464143ab3ae9ba68daae8e7c5c95b969462bbe42e2464d60e7e2698368"}, + {url = "https://files.pythonhosted.org/packages/7e/8a/62334982016006a6820f2117990e1161d15fb05fb2d924b99d303ab2c8ab/yarl-1.9.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:e65610c5792870d45d7b68c677681376fcf9cc1c289f23e8e8b39c1485384185"}, + {url = "https://files.pythonhosted.org/packages/80/53/e0fcf51992fcce66863db5e3fef698a5235257c565a81a46fe3648abae39/yarl-1.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac9bb4c5ce3975aeac288cfcb5061ce60e0d14d92209e780c93954076c7c4367"}, + {url = "https://files.pythonhosted.org/packages/80/57/1dd9bc12ebaae2d08862c23a80662ecd9d63a61777f10e56f78ddb6ad48a/yarl-1.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:de119f56f3c5f0e2fb4dee508531a32b069a5f2c6e827b272d1e0ff5ac040333"}, + {url = "https://files.pythonhosted.org/packages/84/c1/eaebee42cbcace2d5b5eb103cae668dec1c239f5c82b90da4b3b20f39419/yarl-1.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:646d663eb2232d7909e6601f1a9107e66f9791f290a1b3dc7057818fe44fc2b6"}, + {url = "https://files.pythonhosted.org/packages/88/87/081c39d7b136820ca0a6d9c2bd160e91de01b1b4af2de2069bb51b52538c/yarl-1.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4e2d08f07a3d7d3e12549052eb5ad3eab1c349c53ac51c209a0e5991bbada78"}, + {url = "https://files.pythonhosted.org/packages/92/30/aceb4a4cacb850d2cd1841ec1746f42868886ae0523d0b1f3a7cfa31ef57/yarl-1.9.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3da8a678ca8b96c8606bbb8bfacd99a12ad5dd288bc6f7979baddd62f71c63ef"}, + {url = "https://files.pythonhosted.org/packages/92/e4/4f8d1cada85dbf1aab7123125b0d2f997cd30457f3540c72c311ded740e6/yarl-1.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:494053246b119b041960ddcd20fd76224149cfea8ed8777b687358727911dd33"}, + {url = "https://files.pythonhosted.org/packages/97/ae/7fba1ec8384192095731460dd2dd20ecdc19cbeade8996ef997bd989d5a2/yarl-1.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:63c48f6cef34e6319a74c727376e95626f84ea091f92c0250a98e53e62c77c72"}, + {url = "https://files.pythonhosted.org/packages/98/21/9ef4adf36cfac771518c3bf687bc9b92451bdaf01ec770879f19e7e5b3c7/yarl-1.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:50f33040f3836e912ed16d212f6cc1efb3231a8a60526a407aeb66c1c1956dde"}, + {url = "https://files.pythonhosted.org/packages/a6/a4/451ac414ebe15fd6b49a457c7e01f0a06f9b512c36e4388a9cfb26568fea/yarl-1.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:61016e7d582bc46a5378ffdd02cd0314fb8ba52f40f9cf4d9a5e7dbef88dee18"}, + {url = "https://files.pythonhosted.org/packages/a8/ce/95614d05af568504884e866d772c9f03235711f5a4d7fccfae54ce82d39d/yarl-1.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee4afac41415d52d53a9833ebae7e32b344be72835bbb589018c9e938045a560"}, + {url = "https://files.pythonhosted.org/packages/b3/81/fb394392ec748d8fce66212b29dc2fd9b2fd8e30d56d818a6a866708e534/yarl-1.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a83503934c6273806aed765035716216cc9ab4e0364f7f066227e1aaea90b8d0"}, + {url = "https://files.pythonhosted.org/packages/b7/aa/8b53bceea5454d0b5602ffc81aaf3b80cc2e9b793fe1e054f690beb82429/yarl-1.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22a94666751778629f1ec4280b08eb11815783c63f52092a5953faf73be24191"}, + {url = "https://files.pythonhosted.org/packages/b7/f6/4daab7a2c4b3b4bb9fe6496ab658171cddcfc3f1f24154b64002763fa763/yarl-1.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5415d5a4b080dc9612b1b63cba008db84e908b95848369aa1da3686ae27b6d2b"}, + {url = "https://files.pythonhosted.org/packages/ba/7e/dd4d8a9bd9343ecc7b45d80b134549c801dda119032a8af71d3699eaf070/yarl-1.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e7221580dc1db478464cfeef9b03b95c5852cc22894e418562997df0d074ccc"}, + {url = "https://files.pythonhosted.org/packages/bb/98/9af77ac76f61ced2a4fb243c16cca6d941801927a332fb9d95da3899ed70/yarl-1.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3458a24e4ea3fd8930e934c129b676c27452e4ebda80fbe47b56d8c6c7a63a9e"}, + {url = "https://files.pythonhosted.org/packages/c4/0c/7898c35ca4945fdd416e1dadeda985cc391e4f9298ae5e71c3a5cd88e82d/yarl-1.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:838162460b3a08987546e881a2bfa573960bb559dfa739e7800ceeec92e64417"}, + {url = "https://files.pythonhosted.org/packages/c9/d4/a5280faa1b8e9ad3a52ddc4c9aea94dd718f9c55f1e10cfb14580f5ebb45/yarl-1.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:891c0e3ec5ec881541f6c5113d8df0315ce5440e244a716b95f2525b7b9f3608"}, + {url = "https://files.pythonhosted.org/packages/d0/17/875e45f3369af23ae6a299dab56140c4b5398b76757bc3b8388454277ba2/yarl-1.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:83fcc480d7549ccebe9415d96d9263e2d4226798c37ebd18c930fce43dfb9574"}, + {url = "https://files.pythonhosted.org/packages/d5/8b/5a30baa12464d55b308c684a4a953df6b2190f7733c92719f194fcd42421/yarl-1.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:822b30a0f22e588b32d3120f6d41e4ed021806418b4c9f0bc3048b8c8cb3f92a"}, + {url = "https://files.pythonhosted.org/packages/d9/cb/0bfa73fad2049b6315ace645df2bd0682e20f9eb2dac120c2e9183359aa1/yarl-1.9.2-cp311-cp311-win32.whl", hash = "sha256:a60347f234c2212a9f0361955007fcf4033a75bf600a33c88a0a8e91af77c0e8"}, + {url = "https://files.pythonhosted.org/packages/d9/cf/bf402f68933fec675b608a941752b836bc25fa2ec7d6922a1b1f315214b5/yarl-1.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c56986609b057b4839968ba901944af91b8e92f1725d1a2d77cbac6972b9ed1"}, + {url = "https://files.pythonhosted.org/packages/da/1a/94328f245b0f38913338cfa817826def45c193cfc75c76905392f6b484f8/yarl-1.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1012fa63eb6c032f3ce5d2171c267992ae0c00b9e164efe4d73db818465fac3"}, + {url = "https://files.pythonhosted.org/packages/de/3f/5a8fcff69c8628ce4dab00612981f4c0598fb9aabd90d01a1ebb037bb6f6/yarl-1.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:95d2ecefbcf4e744ea952d073c6922e72ee650ffc79028eb1e320e732898d7e8"}, + {url = "https://files.pythonhosted.org/packages/e4/a7/6ffee644828e01c6d6ae177ac6cba56255bb793f79c4d32082a895bf8b91/yarl-1.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e9fdc7ac0d42bc3ea78818557fab03af6181e076a2944f43c38684b4b6bed8e3"}, + {url = "https://files.pythonhosted.org/packages/e5/12/4fd9a60b167b00a58552020babb638f9b43c514da0227df9fc6bdf16948f/yarl-1.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:32f1d071b3f362c80f1a7d322bfd7b2d11e33d2adf395cc1dd4df36c9c243095"}, + {url = "https://files.pythonhosted.org/packages/e8/3b/38f2427f7ee497e169d7f8bd74c92a6ace98594c6a921b619ccc57703fe5/yarl-1.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5610f80cf43b6202e2c33ba3ec2ee0a2884f8f423c8f4f62906731d876ef4fac"}, + {url = "https://files.pythonhosted.org/packages/e9/12/f4989d778d8dd137fd58f55ab3a5501175896b8670239b4822ae44afd4ed/yarl-1.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:662e6016409828ee910f5d9602a2729a8a57d74b163c89a837de3fea050c7582"}, + {url = "https://files.pythonhosted.org/packages/eb/cb/4970008c85810c7d0e154ac5d746451b04476ac1dd85dc538563a1c04698/yarl-1.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c2ad583743d16ddbdf6bb14b5cd76bf43b0d0006e918809d5d4ddf7bde8dd82"}, + {url = "https://files.pythonhosted.org/packages/ee/8d/55467943a172b97c1b5d9569433c1a70f86f1f9b0f1c6574285f8ad02fc2/yarl-1.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:159d81f22d7a43e6eabc36d7194cb53f2f15f498dbbfa8edc8a3239350f59fe7"}, + {url = "https://files.pythonhosted.org/packages/f0/39/e28eec982b1dfd7d6044e5af6f0ca0c1d5760c82f0667a72b0698237d61f/yarl-1.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c027a6e96ef77d401d8d5a5c8d6bc478e8042f1e448272e8d9752cb0aff8b5c8"}, + {url = "https://files.pythonhosted.org/packages/f0/cc/cf416dff5bd88899a567fea556a5f68ab94cdf525ebe122e0bdba478f2c4/yarl-1.9.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:52a25809fcbecfc63ac9ba0c0fb586f90837f5425edfd1ec9f3372b119585e45"}, + {url = "https://files.pythonhosted.org/packages/f1/0c/c2e07b3a37c4363078a1c7d586b251eec191594a2d24d6e09dae33c1368f/yarl-1.9.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:1b1bba902cba32cdec51fca038fd53f8beee88b77efc373968d1ed021024cc04"}, + {url = "https://files.pythonhosted.org/packages/f2/b1/9a6eeba1a3f35188eac6b7b535f20c06df0f48e78705405d86a0407e75f1/yarl-1.9.2-cp38-cp38-win32.whl", hash = "sha256:f7a3d8146575e08c29ed1cd287068e6d02f1c7bdff8970db96683b9591b86ee7"}, + {url = "https://files.pythonhosted.org/packages/f2/ea/6fd350376ed2581d0cdb11018bad0215cf987817dba69ea9a4bf8adbac6e/yarl-1.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8288d7cd28f8119b07dd49b7230d6b4562f9b61ee9a4ab02221060d21136be80"}, + {url = "https://files.pythonhosted.org/packages/fb/2d/060ab740f64ea6ea2088e375c3046839faaf4bbba2b65a5364668bd765e7/yarl-1.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84e0b1599334b1e1478db01b756e55937d4614f8654311eb26012091be109d59"}, + {url = "https://files.pythonhosted.org/packages/fe/7d/9d85f658b6f7c041ca3ba371d133040c4dc41eb922aef0a6ba917291d187/yarl-1.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aff634b15beff8902d1f918012fc2a42e0dbae6f469fce134c8a0dc51ca423bb"}, +] +"zipp 3.16.2" = [ + {url = "https://files.pythonhosted.org/packages/8c/08/d3006317aefe25ea79d3b76c9650afabaf6d63d1c8443b236e7405447503/zipp-3.16.2-py3-none-any.whl", hash = "sha256:679e51dd4403591b2d6838a48de3d283f3d188412a9782faadf845f298736ba0"}, + {url = "https://files.pythonhosted.org/packages/e2/45/f3b987ad5bf9e08095c1ebe6352238be36f25dd106fde424a160061dce6d/zipp-3.16.2.tar.gz", hash = "sha256:ebc15946aa78bd63458992fc81ec3b6f7b1e92d51c35e6de1c3804e73b799147"}, +] diff --git a/datajunction-server/pyproject.toml b/datajunction-server/pyproject.toml new file mode 100644 index 000000000..5629ccd80 --- /dev/null +++ b/datajunction-server/pyproject.toml @@ -0,0 +1,103 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.pdm.build] +includes = ["dj"] + +[project] +name = "datajunction-server" +dynamic = ["version"] +description = "DataJunction server library for running to a DataJunction server" +authors = [ + {name = "DataJunction Authors", email = "yian.shang@gmail.com"}, +] +dependencies = [ + "alembic>=1.10.3", + "SQLAlchemy-Utils<1.0.0,>=0.40.0", + "accept-types<1.0.0,>=0.4.1", + "antlr4-python3-runtime==4.12.0", + "asciidag<1.0.0,>=0.2.0", + "cachelib<1.0.0,>=0.10.2", + "celery<6.0.0,>=5.2.7", + "fastapi<0.80.0,>=0.79.0", + "msgpack<2.0.0,>=1.0.5", + "opentelemetry-instrumentation-fastapi==0.38b0", + "python-dotenv<1.0.0,>=0.19.0", + "redis<5.0.0,>=4.5.4", + "requests<=2.29.0,>=2.28.2", + "rich<14.0.0,>=13.3.3", + "SQLAlchemy<2.0.0,>=1.4.41", + "sqlmodel<1.0.0,>=0.0.8", + "sqlparse<1.0.0,>=0.4.3", + "sse-starlette>=1.6.0", + "yarl<2.0.0,>=1.8.2", + "python-multipart>=0.0.6", + "passlib>=1.7.4", + "python-jose>=3.3.0", + "cryptography>=41.0.3", + "bcrypt>=4.0.1", + "line-profiler>=4.0.3", + "cachetools>=5.3.1", + "types-cachetools>=5.3.0.6", + "strawberry-graphql>=0.204.0", +] +requires-python = ">=3.8,<4.0" +readme = "README.md" +license = {text = "MIT"} +classifiers = [ + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent" +] + +[project.optional-dependencies] +uvicorn = [ + "uvicorn[standard]>=0.21.1", +] + +[project.entry-points.'superset.db_engine_specs'] +dj = 'datajunction_server.superset:DJEngineSpec' + +[tool.hatch.version] +path = "datajunction_server/__about__.py" + +[project.urls] +repository = "https://github.com/DataJunction/dj" + +[tool.pdm.dev-dependencies] +test = [ + "codespell>=2.2.4", + "freezegun>=1.2.2", + "pre-commit>=3.2.2", + "pylint>=2.17.3", + "pytest-asyncio>=0.15.1", + "pytest-cov>=4.0.0", + "pytest-integration>=0.2.2", + "pytest-mock>=3.10.0", + "pytest>=7.3.0", + "requests-mock>=1.10.0", + "typing-extensions>=4.5.0", + "pytest-xdist>=3.3.0", +] + +[[tool.pdm.autoexport]] +filename = "requirements/docker.txt" +groups = ["default", "uvicorn"] +without-hashes = true + +[[tool.pdm.autoexport]] +filename = "requirements/test.txt" +groups = ["default", "test"] +without-hashes = true + +[tool.coverage.run] +source = ['datajunction_server/'] + +[tool.isort] +src_paths = ["datajunction_server/", "tests/"] +profile = 'black' diff --git a/datajunction-server/requirements/docker.txt b/datajunction-server/requirements/docker.txt new file mode 100644 index 000000000..a529cd155 --- /dev/null +++ b/datajunction-server/requirements/docker.txt @@ -0,0 +1,84 @@ +# This file is @generated by PDM. +# Please do not edit it manually. + +accept-types==0.4.1 +alembic==1.11.1 +amqp==5.1.1 +antlr4-python3-runtime==4.12.0 +anyio==3.7.1 +asciidag==0.2.0 +asgiref==3.7.2 +async-timeout==4.0.2 +bcrypt==4.0.1 +billiard==4.1.0 +cachelib==0.10.2 +cachetools==5.3.1 +celery==5.3.1 +certifi==2023.5.7 +cffi==1.15.1 +charset-normalizer==3.2.0 +click==8.1.6 +click-didyoumean==0.3.0 +click-plugins==1.1.1 +click-repl==0.3.0 +cryptography==41.0.3 +deprecated==1.2.14 +ecdsa==0.18.0 +exceptiongroup==1.1.2 +fastapi==0.79.1 +h11==0.14.0 +httptools==0.6.0 +idna==3.4 +importlib-metadata==6.8.0 +kombu==5.3.1 +line-profiler==4.0.3 +Mako==1.2.4 +markdown-it-py==3.0.0 +MarkupSafe==2.1.3 +mdurl==0.1.2 +msgpack==1.0.5 +multidict==6.0.4 +opentelemetry-api==1.19.0 +opentelemetry-instrumentation==0.38b0 +opentelemetry-instrumentation-asgi==0.38b0 +opentelemetry-instrumentation-fastapi==0.38b0 +opentelemetry-semantic-conventions==0.38b0 +opentelemetry-util-http==0.38b0 +passlib==1.7.4 +prompt-toolkit==3.0.39 +pyasn1==0.5.0 +pycparser==2.21 +pydantic==1.10.11 +pygments==2.15.1 +python-dateutil==2.8.2 +python-dotenv==0.21.1 +python-jose==3.3.0 +python-multipart==0.0.6 +pyyaml==6.0.1 +redis==4.6.0 +requests==2.29.0 +rich==13.4.2 +rsa==4.9 +setuptools==68.0.0 +six==1.16.0 +sniffio==1.3.0 +SQLAlchemy==1.4.41 +SQLAlchemy-Utils==0.41.1 +sqlalchemy2-stubs==0.0.2a35 +sqlmodel==0.0.8 +sqlparse==0.4.4 +sse-starlette==1.6.1 +starlette==0.19.1 +types-cachetools==5.3.0.6 +typing-extensions==4.7.1 +tzdata==2023.3 +urllib3==1.26.16 +uvicorn==0.23.1 +uvloop==0.17.0 +vine==5.0.0 +watchfiles==0.19.0 +wcwidth==0.2.6 +websockets==11.0.3 +wrapt==1.15.0 +yarl==1.9.2 +zipp==3.16.2 diff --git a/datajunction-server/requirements/test.txt b/datajunction-server/requirements/test.txt new file mode 100644 index 000000000..c12aa6e68 --- /dev/null +++ b/datajunction-server/requirements/test.txt @@ -0,0 +1,108 @@ +# This file is @generated by PDM. +# Please do not edit it manually. + +accept-types==0.4.1 +alembic==1.11.1 +amqp==5.1.1 +antlr4-python3-runtime==4.12.0 +anyio==3.7.1 +asciidag==0.2.0 +asgiref==3.7.2 +astroid==2.15.6 +async-timeout==4.0.2 +bcrypt==4.0.1 +billiard==4.1.0 +cachelib==0.10.2 +cachetools==5.3.1 +celery==5.3.1 +certifi==2023.5.7 +cffi==1.15.1 +cfgv==3.3.1 +charset-normalizer==3.2.0 +click==8.1.6 +click-didyoumean==0.3.0 +click-plugins==1.1.1 +click-repl==0.3.0 +codespell==2.2.5 +coverage==7.2.7 +cryptography==41.0.3 +deprecated==1.2.14 +dill==0.3.6 +distlib==0.3.7 +ecdsa==0.18.0 +exceptiongroup==1.1.2 +execnet==2.0.2 +fastapi==0.79.1 +filelock==3.12.2 +freezegun==1.2.2 +identify==2.5.25 +idna==3.4 +importlib-metadata==6.8.0 +iniconfig==2.0.0 +isort==5.12.0 +kombu==5.3.1 +lazy-object-proxy==1.9.0 +line-profiler==4.0.3 +Mako==1.2.4 +markdown-it-py==3.0.0 +MarkupSafe==2.1.3 +mccabe==0.7.0 +mdurl==0.1.2 +msgpack==1.0.5 +multidict==6.0.4 +nodeenv==1.8.0 +opentelemetry-api==1.19.0 +opentelemetry-instrumentation==0.38b0 +opentelemetry-instrumentation-asgi==0.38b0 +opentelemetry-instrumentation-fastapi==0.38b0 +opentelemetry-semantic-conventions==0.38b0 +opentelemetry-util-http==0.38b0 +packaging==23.1 +passlib==1.7.4 +platformdirs==3.9.1 +pluggy==1.2.0 +pre-commit==3.3.3 +prompt-toolkit==3.0.39 +pyasn1==0.5.0 +pycparser==2.21 +pydantic==1.10.11 +pygments==2.15.1 +pylint==2.17.4 +pytest==7.4.0 +pytest-asyncio==0.21.1 +pytest-cov==4.1.0 +pytest-integration==0.2.3 +pytest-mock==3.11.1 +pytest-xdist==3.3.1 +python-dateutil==2.8.2 +python-dotenv==0.21.1 +python-jose==3.3.0 +python-multipart==0.0.6 +pyyaml==6.0.1 +redis==4.6.0 +requests==2.29.0 +requests-mock==1.11.0 +rich==13.4.2 +rsa==4.9 +setuptools==68.0.0 +six==1.16.0 +sniffio==1.3.0 +SQLAlchemy==1.4.41 +SQLAlchemy-Utils==0.41.1 +sqlalchemy2-stubs==0.0.2a35 +sqlmodel==0.0.8 +sqlparse==0.4.4 +sse-starlette==1.6.1 +starlette==0.19.1 +tomli==2.0.1 +tomlkit==0.11.8 +types-cachetools==5.3.0.6 +typing-extensions==4.7.1 +tzdata==2023.3 +urllib3==1.26.16 +vine==5.0.0 +virtualenv==20.24.1 +wcwidth==0.2.6 +wrapt==1.15.0 +yarl==1.9.2 +zipp==3.16.2 diff --git a/datajunction-server/scripts/docs-snippets.js b/datajunction-server/scripts/docs-snippets.js new file mode 100644 index 000000000..483cc2b61 --- /dev/null +++ b/datajunction-server/scripts/docs-snippets.js @@ -0,0 +1,153 @@ +const { DJClient } = require("datajunction") + +const dj = new DJClient("http://localhost:8000") + +Promise.resolve().then( + () => { + return dj.namespaces.create("default").then(data => console.log(data)) + } +).then( + () => { + return dj.catalogs.create({"name": "warehouse"}).then(data => console.log(data)) + } +).then( + () => { + return dj.engines.create({ + name: "duckdb", + version: "0.7.1", + dialect: "spark", + }).then(data => console.log(data)) + } +).then( + () => { + return dj.catalogs.addEngine("warehouse", "duckdb", "0.7.1").then(data => console.log(data)) + } +).then( + () => { + return dj.sources.create({ + name: "default.repair_orders", + description: "Repair orders", + mode: "published", + catalog: "warehouse", + schema_: "roads", + table: "repair_orders", + columns: [ + {name: "repair_order_id", type: "int"}, + {name: "municipality_id", type: "string"}, + {name: "hard_hat_id", type: "int"}, + {name: "order_date", type: "timestamp"}, + {name: "required_date", type: "timestamp"}, + {name: "dispatched_date", type: "timestamp"}, + {name: "dispatcher_id", type: "int"} + ] + }).then(data => console.log(data)) + } +).then( + () => { + return dj.transforms.create( + { + name: "default.repair_orders_w_dispatchers", + mode: "published", + description: "Repair orders that have a dispatcher", + query: ` + SELECT + repair_order_id, + municipality_id, + hard_hat_id, + dispatcher_id + FROM default.repair_orders + WHERE dispatcher_id IS NOT NULL + ` + } + ).then(data => console.log(data)) + } +).then( + () => { + return dj.sources.create( + { + name: "default.dispatchers", + mode: "published", + description: "Contact list for dispatchers", + catalog: "warehouse", + schema_: "roads", + table: "dispatchers", + columns: [ + {name: "dispatcher_id", type: "int"}, + {name: "company_name", type: "string"}, + {name: "phone", type: "string"} + ] + } + ).then(data => console.log(data)) + } +).then( + () => { + return dj.dimensions.create( + { + name: "default.all_dispatchers", + mode: "published", + description: "All dispatchers", + query: ` + SELECT + dispatcher_id, + company_name, + phone + FROM default.dispatchers + `, + primary_key: ["dispatcher_id"] + } + ).then(data => console.log(data)) + } +).then( + () => { + return dj.dimensions.link("default.repair_orders", "dispatcher_id", "default.all_dispatchers", "dispatcher_id").then(data => console.log(data)) + } +).then( + () => { + return dj.metrics.create( + { + name: "default.num_repair_orders", + description: "Number of repair orders", + mode: "published", + query: ` + SELECT + count(repair_order_id) as num_repair_orders + FROM default.repair_orders + ` + } + ).then(data => console.log(data)) + } +).then( + () => { + return dj.cubes.create( + { + name: "default.repairs_cube", + mode: "published", + display_name: "Repairs for each company", + description: "Cube of the number of repair orders grouped by dispatcher companies", + metrics: [ + "default.num_repair_orders" + ], + dimensions: [ + "default.all_dispatchers.company_name" + ], + filters: ["default.all_dispatchers.company_name IS NOT NULL"] + } + ).then(data => console.log(data)) + } +).then( + () => { + return dj.sql.get( + metrics=["default.num_repair_orders"], + dimensions=["default.all_dispatchers.company_name"], + filters=["default.all_dispatchers.company_name IS NOT NULL"] + ).then(data => console.log(data)) + } +).then( + () => { + return dj.data.get( + metrics=["default.num_repair_orders"], + dimensions=["default.all_dispatchers.company_name"], + filters=["default.all_dispatchers.company_name IS NOT NULL"] + ).then(data => console.log(data)) + } +) diff --git a/datajunction-server/scripts/docs-snippets.sh b/datajunction-server/scripts/docs-snippets.sh new file mode 100755 index 000000000..71c5c2863 --- /dev/null +++ b/datajunction-server/scripts/docs-snippets.sh @@ -0,0 +1,149 @@ +# Creating a namespace +printf "\n" +curl -X POST http://localhost:8000/namespaces/default/ + +# Creating a catalog +printf "\n" +curl -X POST http://localhost:8000/catalogs/ \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -d '{ + "name": "warehouse" +}' + +# Creating an engine +printf "\n" +curl -X 'POST' \ + 'http://localhost:8000/engines/' \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -d '{ + "name": "duckdb", + "version": "0.7.1", + "dialect": "spark" +}' + +# Attaching an engine to a catalog +printf "\n" +curl -X 'POST' \ + 'http://localhost:8000/catalogs/warehouse/engines/' \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -d '[{ + "name": "duckdb", + "version": "0.7.1" +}]' + +# Creating a source node +printf "\n" +curl -X POST http://localhost:8000/nodes/source/ \ +-H 'Content-Type: application/json' \ +-d '{ + "name": "default.repair_orders", + "description": "Repair orders", + "mode": "published", + "catalog": "warehouse", + "schema_": "roads", + "table": "repair_orders", + "columns": [ + {"name": "repair_order_id", "type": "int"}, + {"name": "municipality_id", "type": "string"}, + {"name": "hard_hat_id", "type": "int"}, + {"name": "order_date", "type": "timestamp"}, + {"name": "required_date", "type": "timestamp"}, + {"name": "dispatched_date", "type": "timestamp"}, + {"name": "dispatcher_id", "type": "int"} + ] +}' + +# Creating a transform node +printf "\n" +curl -X POST http://localhost:8000/nodes/transform/ \ +-H 'Content-Type: application/json' \ +-d '{ + "name": "default.repair_orders_w_dispatchers", + "description": "Repair orders that have a dispatcher", + "mode": "published", + "query": "SELECT repair_order_id, municipality_id, hard_hat_id, dispatcher_id FROM default.repair_orders WHERE dispatcher_id IS NOT NULL" +}' + +# Creating a dimension node (part 1, creating a source first) +printf "\n" +curl -X 'POST' \ + 'http://localhost:8000/nodes/source/' \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -d '{ + "columns": [ + {"name": "dispatcher_id", "type": "int"}, + {"name": "company_name", "type": "string"}, + {"name": "phone", "type": "string"} + ], + "description": "Contact list for dispatchers", + "mode": "published", + "name": "default.dispatchers", + "catalog": "warehouse", + "schema_": "roads", + "table": "dispatchers" + }' + +# Creating a dimension node (part 2, creating the dimension node) +printf "\n" +curl -X POST http://localhost:8000/nodes/dimension/ \ +-H 'Content-Type: application/json' \ +-d '{ + "name": "default.all_dispatchers", + "description": "All dispatchers", + "mode": "published", + "query": "SELECT dispatcher_id, company_name, phone FROM default.dispatchers", + "primary_key": ["dispatcher_id"] +}' + +# Creating a dimension node (part 3, linking a dimension to a column) +printf "\n" +curl -X 'POST' \ + 'http://localhost:8000/nodes/default.repair_orders/columns/dispatcher_id/?dimension=default.all_dispatchers&dimension_column=dispatcher_id' \ + -H 'accept: application/json' + +# Creating a metric node +printf "\n" +curl -X POST http://localhost:8000/nodes/metric/ \ +-H 'Content-Type: application/json' \ +-d '{ + "name": "default.num_repair_orders", + "description": "Number of repair orders", + "mode": "published", + "query": "SELECT count(repair_order_id) as num_repair_orders FROM default.repair_orders" +}' + +# Creating a cube +printf "\n" +curl -X POST http://localhost:8000/nodes/cube/ \ +-H 'Content-Type: application/json' \ +-d '{ + "name": "default.repairs_cube", + "mode": "published", + "display_name": "Repairs for each company", + "description": "Cube of the number of repair orders grouped by dispatcher companies", + "metrics": [ + "default.num_repair_orders" + ], + "dimensions": [ + "default.all_dispatchers.company_name" + ], + "filters": ["default.all_dispatchers.company_name IS NOT NULL"], +}' + +# Get SQL for metrics +printf "\n" +curl -X 'GET' \ + 'http://localhost:8000/sql/?metrics=default.num_repair_orders&dimensions=default.all_dispatchers.company_name&filters=default.all_dispatchers.company_name%20IS%20NOT%20NULL' \ + -H 'accept: application/json' + +# Get data for metrics +printf "\n" +curl -X 'GET' \ + 'http://localhost:8000/data/?metrics=default.num_repair_orders&dimensions=default.all_dispatchers.company_name&filters=default.all_dispatchers.company_name%20IS%20NOT%20NULL' \ + -H 'accept: application/json' + +printf "\n" diff --git a/datajunction-server/scripts/generate-openapi.py b/datajunction-server/scripts/generate-openapi.py new file mode 100755 index 000000000..affbfaccd --- /dev/null +++ b/datajunction-server/scripts/generate-openapi.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# pylint: skip-file + +import argparse +import json + +from datajunction_server.api.main import app + + +def save_openapi_spec(f: str): + spec = app.openapi() + with open(f, "w") as outfile: + outfile.write(json.dumps(spec, indent=4)) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Generate a file containing the OpenAPI spec for a DJ server", + ) + parser.add_argument( + "-o", + "--output-file", + dest="filename", + required=True, + metavar="FILE", + ) + args = vars(parser.parse_args()) + save_openapi_spec(f=args["filename"]) diff --git a/datajunction-server/tests/__init__.py b/datajunction-server/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/datajunction-server/tests/api/__init__.py b/datajunction-server/tests/api/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/datajunction-server/tests/api/attributes_test.py b/datajunction-server/tests/api/attributes_test.py new file mode 100644 index 000000000..c23ec97ff --- /dev/null +++ b/datajunction-server/tests/api/attributes_test.py @@ -0,0 +1,124 @@ +""" +Tests for the attributes API. +""" +from unittest.mock import ANY + +from fastapi.testclient import TestClient + + +def test_adding_new_attribute( + client: TestClient, +) -> None: + """ + Test adding an attribute. + """ + response = client.post( + "/attributes/", + json={ + "namespace": "custom", + "name": "internal", + "description": "Column for internal use only", + "allowed_node_types": ["source"], + }, + ) + data = response.json() + assert response.status_code == 201 + assert data == { + "id": ANY, + "namespace": "custom", + "name": "internal", + "description": "Column for internal use only", + "uniqueness_scope": [], + "allowed_node_types": ["source"], + } + + response = client.post( + "/attributes/", + json={ + "namespace": "custom", + "name": "internal", + "description": "Column for internal use only", + "allowed_node_types": ["source"], + }, + ) + assert response.status_code == 500 + data = response.json() + assert data == { + "message": "Attribute type `internal` already exists!", + "errors": [], + "warnings": [], + } + + response = client.post( + "/attributes/", + json={ + "namespace": "system", + "name": "logging", + "description": "Column for logging use only", + "allowed_node_types": ["source"], + }, + ) + assert response.status_code == 500 + data = response.json() + assert data == { + "message": "Cannot use `system` as the attribute type namespace as it is reserved.", + "errors": [], + "warnings": [], + } + + +def test_list_attributes( + client: TestClient, +) -> None: + """ + Test listing attributes. These should contain the default attributes. + """ + response = client.get("/attributes/") + assert response.status_code == 200 + data = response.json() + data = { + type_["name"]: {k: type_[k] for k in (type_.keys() - {"id"})} for type_ in data + } + assert data == { + "primary_key": { + "namespace": "system", + "uniqueness_scope": [], + "allowed_node_types": ["source", "transform", "dimension"], + "name": "primary_key", + "description": "Points to a column which is part of the primary key of the node", + }, + "expired_time": { + "namespace": "system", + "uniqueness_scope": ["node", "column_type"], + "allowed_node_types": ["dimension"], + "name": "expired_time", + "description": "Points to a column which represents the expired time of a row in " + "a dimension node. Used to facilitate proper joins with fact nodes " + "on event time.", + }, + "effective_time": { + "namespace": "system", + "uniqueness_scope": ["node", "column_type"], + "allowed_node_types": ["dimension"], + "name": "effective_time", + "description": "Points to a column which represents the effective time of a row " + "in a dimension node. Used to facilitate proper joins with fact " + "nodes on event time.", + }, + "event_time": { + "namespace": "system", + "uniqueness_scope": ["node", "column_type"], + "allowed_node_types": ["source", "transform"], + "name": "event_time", + "description": "Points to a column which represents the time of the event in a " + "given fact related node. Used to facilitate proper joins with " + "dimension node to match the desired effect.", + }, + "dimension": { + "namespace": "system", + "uniqueness_scope": [], + "allowed_node_types": ["source", "transform"], + "name": "dimension", + "description": "Points to a dimension attribute column", + }, + } diff --git a/datajunction-server/tests/api/catalog_test.py b/datajunction-server/tests/api/catalog_test.py new file mode 100644 index 000000000..f3ee0b5ec --- /dev/null +++ b/datajunction-server/tests/api/catalog_test.py @@ -0,0 +1,392 @@ +""" +Tests for the catalog API. +""" + +from fastapi.testclient import TestClient + + +def test_catalog_adding_a_new_catalog( + client: TestClient, +) -> None: + """ + Test adding a catalog + """ + response = client.post( + "/catalogs/", + json={ + "name": "dev", + }, + ) + data = response.json() + assert response.status_code == 201 + assert data == {"name": "dev", "engines": []} + + +def test_catalog_list( + client: TestClient, +) -> None: + """ + Test listing catalogs + """ + response = client.post( + "/engines/", + json={ + "name": "spark", + "version": "3.3.1", + "dialect": "spark", + }, + ) + assert response.status_code == 201 + + response = client.post( + "/catalogs/", + json={ + "name": "dev", + "engines": [ + { + "name": "spark", + "version": "3.3.1", + "dialect": "spark", + }, + ], + }, + ) + assert response.status_code == 201 + + response = client.post( + "/catalogs/", + json={ + "name": "test", + }, + ) + assert response.status_code == 201 + + response = client.post( + "/catalogs/", + json={ + "name": "prod", + }, + ) + assert response.status_code == 201 + + response = client.get("/catalogs/") + assert response.status_code == 200 + assert response.json() == [ + { + "name": "dev", + "engines": [ + {"name": "spark", "version": "3.3.1", "uri": None, "dialect": "spark"}, + ], + }, + {"name": "test", "engines": []}, + {"name": "prod", "engines": []}, + ] + + +def test_catalog_get_catalog( + client: TestClient, +) -> None: + """ + Test getting a catalog + """ + response = client.post( + "/engines/", + json={ + "name": "spark", + "version": "3.3.1", + "dialect": "spark", + }, + ) + assert response.status_code == 201 + + response = client.post( + "/catalogs/", + json={ + "name": "dev", + "engines": [ + { + "name": "spark", + "version": "3.3.1", + "dialect": "spark", + }, + ], + }, + ) + assert response.status_code == 201 + + response = client.get( + "/catalogs/dev", + ) + assert response.status_code == 200 + data = response.json() + assert data == { + "name": "dev", + "engines": [ + {"name": "spark", "uri": None, "version": "3.3.1", "dialect": "spark"}, + ], + } + + +def test_catalog_adding_a_new_catalog_with_engines( + client: TestClient, +) -> None: + """ + Test adding a catalog with engines + """ + response = client.post( + "/engines/", + json={ + "name": "spark", + "uri": None, + "version": "3.3.1", + "dialect": "spark", + }, + ) + data = response.json() + assert response.status_code == 201 + + response = client.post( + "/catalogs/", + json={ + "name": "dev", + "engines": [ + { + "name": "spark", + "version": "3.3.1", + "dialect": "spark", + }, + ], + }, + ) + data = response.json() + assert response.status_code == 201 + assert data == { + "name": "dev", + "engines": [ + { + "name": "spark", + "uri": None, + "version": "3.3.1", + "dialect": "spark", + }, + ], + } + + +def test_catalog_adding_a_new_catalog_then_attaching_engines( + client: TestClient, +) -> None: + """ + Test adding a catalog then attaching a catalog + """ + response = client.post( + "/engines/", + json={ + "name": "spark", + "uri": None, + "version": "3.3.1", + "dialect": "spark", + }, + ) + data = response.json() + assert response.status_code == 201 + + response = client.post( + "/catalogs/", + json={ + "name": "dev", + }, + ) + assert response.status_code == 201 + + client.post( + "/catalogs/dev/engines/", + json=[ + { + "name": "spark", + "version": "3.3.1", + "dialect": "spark", + }, + ], + ) + + response = client.get("/catalogs/dev/") + data = response.json() + assert data == { + "name": "dev", + "engines": [ + { + "name": "spark", + "uri": None, + "version": "3.3.1", + "dialect": "spark", + }, + ], + } + + +def test_catalog_adding_without_duplicating( + client: TestClient, +) -> None: + """ + Test adding a catalog and having existing catalogs not re-added + """ + response = client.post( + "/engines/", + json={ + "name": "spark", + "uri": None, + "version": "2.4.4", + "dialect": "spark", + }, + ) + data = response.json() + assert response.status_code == 201 + + response = client.post( + "/engines/", + json={ + "name": "spark", + "version": "3.3.0", + "dialect": "spark", + }, + ) + data = response.json() + assert response.status_code == 201 + + response = client.post( + "/engines/", + json={ + "name": "spark", + "version": "3.3.1", + "dialect": "spark", + }, + ) + data = response.json() + assert response.status_code == 201 + + response = client.post( + "/catalogs/", + json={ + "name": "dev", + "dialect": "spark", + }, + ) + assert response.status_code == 201 + + response = client.post( + "/catalogs/dev/engines/", + json=[ + { + "name": "spark", + "version": "2.4.4", + "dialect": "spark", + }, + { + "name": "spark", + "version": "3.3.0", + "dialect": "spark", + }, + { + "name": "spark", + "version": "3.3.1", + "dialect": "spark", + }, + ], + ) + assert response.status_code == 201 + + response = client.post( + "/catalogs/dev/engines/", + json=[ + { + "name": "spark", + "version": "2.4.4", + "dialect": "spark", + }, + { + "name": "spark", + "version": "3.3.0", + "dialect": "spark", + }, + { + "name": "spark", + "version": "3.3.1", + "dialect": "spark", + }, + ], + ) + assert response.status_code == 201 + data = response.json() + assert data == { + "name": "dev", + "engines": [ + { + "name": "spark", + "uri": None, + "version": "2.4.4", + "dialect": "spark", + }, + { + "name": "spark", + "uri": None, + "version": "3.3.0", + "dialect": "spark", + }, + { + "name": "spark", + "uri": None, + "version": "3.3.1", + "dialect": "spark", + }, + ], + } + + +def test_catalog_raise_on_adding_a_new_catalog_with_nonexistent_engines( + client: TestClient, +) -> None: + """ + Test raising an error when adding a catalog with engines that do not exist + """ + response = client.post( + "/catalogs/", + json={ + "name": "dev", + "engines": [ + { + "name": "spark", + "version": "4.0.0", + "dialect": "spark", + }, + ], + }, + ) + data = response.json() + assert response.status_code == 404 + assert data == {"detail": "Engine not found: `spark` version `4.0.0`"} + + +def test_catalog_raise_on_catalog_already_exists( + client: TestClient, +) -> None: + """ + Test raise on catalog already exists + """ + response = client.post( + "/catalogs/", + json={ + "name": "dev", + }, + ) + assert response.status_code == 201 + + response = client.post( + "/catalogs/", + json={ + "name": "dev", + }, + ) + data = response.json() + assert response.status_code == 409 + assert data == {"detail": "Catalog already exists: `dev`"} diff --git a/datajunction-server/tests/api/client_test.py b/datajunction-server/tests/api/client_test.py new file mode 100644 index 000000000..bed3aa557 --- /dev/null +++ b/datajunction-server/tests/api/client_test.py @@ -0,0 +1,210 @@ +""" +Tests for client code generator. +""" + +from fastapi.testclient import TestClient + + +def test_generated_python_client_code_new_metric(client_with_examples: TestClient): + """ + Test generating Python client code for creating a new metric + """ + response = client_with_examples.get( + "/datajunction-clients/python/new_node/default.num_repair_orders", + ) + assert ( + response.json() + == """dj = DJBuilder(DJ_URL) + +num_repair_orders = dj.create_metric( + description="Number of repair orders", + display_name="Default: Num Repair Orders", + name="default.num_repair_orders", + primary_key=[], + query=\"\"\"SELECT count(repair_order_id) default_DOT_num_repair_orders""" + + " \n" + + """ FROM default.repair_orders + +\"\"\" +)""" + ) + + +def test_generated_python_client_code_new_source(client_with_examples: TestClient): + """ + Test generating Python client code for creating a new source + """ + response = client_with_examples.get( + "/datajunction-clients/python/new_node/default.repair_order_details", + ) + assert ( + response.json() + == """dj = DJBuilder(DJ_URL) + +repair_order_details = dj.create_source( + description="Details on repair orders", + display_name="Default: Repair Order Details", + name="default.repair_order_details", + primary_key=[], + schema_="roads", + table="repair_order_details" +)""" + ) + + +def test_generated_python_client_code_new_dimension(client_with_examples: TestClient): + """ + Test generating Python client code for creating a new dimension + """ + response = client_with_examples.get( + "/datajunction-clients/python/new_node/default.repair_order", + ) + assert ( + response.json() + == """dj = DJBuilder(DJ_URL) + +repair_order = dj.create_dimension( + description="Repair order dimension", + display_name="Default: Repair Order", + name="default.repair_order", + primary_key=['repair_order_id'], + query=\"\"\" + SELECT + repair_order_id, + municipality_id, + hard_hat_id, + order_date, + required_date, + dispatched_date, + dispatcher_id + FROM default.repair_orders + \"\"\" +)""" + ) + + +def test_generated_python_client_code_new_cube(client_with_examples: TestClient): + """ + Test generating Python client code for creating a new dimension + """ + client_with_examples.post( + "/nodes/cube/", + json={ + "metrics": ["default.num_repair_orders", "default.total_repair_cost"], + "dimensions": [ + "default.hard_hat.country", + "default.hard_hat.city", + ], + "description": "Cube of various metrics related to repairs", + "mode": "published", + "name": "default.repairs_cube", + }, + ) + response = client_with_examples.get( + "/datajunction-clients/python/new_node/default.repairs_cube", + ) + assert ( + response.json() + == """dj = DJBuilder(DJ_URL) + +repairs_cube = dj.create_cube( + description="Cube of various metrics related to repairs", + display_name="Default: Repairs Cube", + name="default.repairs_cube", + primary_key=[], + metrics=["default.num_repair_orders", "default.total_repair_cost"], + dimensions=["default.hard_hat.country", "default.hard_hat.city"] +)""" + ) + + +def test_generated_python_client_code_adding_materialization( + client_with_query_service: TestClient, +): + """ + Test that generating python client code for adding materialization works + """ + client_with_query_service.post( + "/engines/", + json={ + "name": "spark", + "version": "2.4.4", + "dialect": "spark", + }, + ) + client_with_query_service.post( + "/nodes/basic.transform.country_agg/materialization/", + json={ + "engine": { + "name": "spark", + "version": "2.4.4", + }, + "config": { + "partitions": [ + { + "name": "country", + "values": ["DE", "MY"], + "type_": "categorical", + }, + ], + }, + "schedule": "0 * * * *", + }, + ) + response = client_with_query_service.get( + "/datajunction-clients/python/add_materialization/basic.transform.country_agg/country_3491792861", # pylint: disable=line-too-long + ) + assert ( + response.json() + == """dj = DJBuilder(DJ_URL) + +country_agg = dj.transform( + "basic.transform.country_agg" +) +materialization = MaterializationConfig( + engine=Engine( + name="spark", + version="2.4.4", + ), + schedule="0 * * * *", + config={ + "partitions": [ + { + "name": "country", + "values": [ + "DE", + "MY" + ], + "range": null, + "expression": null, + "type_": "categorical" + } + ], + "spark": {} + }, +) +country_agg.add_materialization( + materialization +)""" + ) + + +def test_generated_python_client_code_link_dimension(client_with_examples: TestClient): + """ + Test generating Python client code for creating a new dimension + """ + response = client_with_examples.get( + "/datajunction-clients/python/link_dimension/foo.bar.repair_orders/" + "municipality_id/foo.bar.municipality_dim/", + ) + assert ( + response.json() + == """dj = DJBuilder(DJ_URL) +repair_orders = dj.source( + "foo.bar.repair_orders" +) +repair_orders.link_dimension( + "municipality_id", + "foo.bar.municipality_dim", +)""" + ) diff --git a/datajunction-server/tests/api/cubes_test.py b/datajunction-server/tests/api/cubes_test.py new file mode 100644 index 000000000..6778b053b --- /dev/null +++ b/datajunction-server/tests/api/cubes_test.py @@ -0,0 +1,1261 @@ +# pylint: disable=too-many-lines +""" +Tests for the cubes API. +""" +from typing import Dict, Iterator + +import pytest +from fastapi.testclient import TestClient + +from datajunction_server.service_clients import QueryServiceClient +from tests.sql.utils import compare_query_strings + + +def test_read_cube(client_with_examples: TestClient) -> None: + """ + Test ``GET /cubes/{name}``. + """ + # Create a cube + response = client_with_examples.post( + "/nodes/cube/", + json={ + "metrics": ["default.number_of_account_types"], + "dimensions": ["default.account_type.account_type_name"], + "filters": [], + "description": "A cube of number of accounts grouped by account type", + "mode": "published", + "name": "default.number_of_accounts_by_account_type", + }, + ) + assert response.status_code == 201 + data = response.json() + assert data["version"] == "v1.0" + assert data["type"] == "cube" + assert data["name"] == "default.number_of_accounts_by_account_type" + assert data["display_name"] == "Default: Number Of Accounts By Account Type" + + # Read the cube + response = client_with_examples.get( + "/cubes/default.number_of_accounts_by_account_type", + ) + assert response.status_code == 200 + data = response.json() + assert data["type"] == "cube" + assert data["name"] == "default.number_of_accounts_by_account_type" + assert data["display_name"] == "Default: Number Of Accounts By Account Type" + assert data["version"] == "v1.0" + assert data["description"] == "A cube of number of accounts grouped by account type" + assert compare_query_strings( + data["query"], + """ + WITH m0_default_DOT_number_of_account_types AS ( + SELECT + default_DOT_account_type.account_type_name, + count(default_DOT_account_type.id) default_DOT_number_of_account_types + FROM ( + SELECT + default_DOT_account_type_table.account_type_classification, + default_DOT_account_type_table.account_type_name, + default_DOT_account_type_table.id + FROM accounting.account_type_table AS default_DOT_account_type_table + ) AS default_DOT_account_type + GROUP BY default_DOT_account_type.account_type_name + ) + SELECT + m0_default_DOT_number_of_account_types.default_DOT_number_of_account_types, + m0_default_DOT_number_of_account_types.account_type_name + FROM m0_default_DOT_number_of_account_types + """, + ) + + +def test_create_invalid_cube(client_with_examples: TestClient): + """ + Check that creating a cube with a query fails appropriately + """ + response = client_with_examples.post( + "/nodes/cube/", + json={ + "description": "A cube of number of accounts grouped by account type", + "mode": "published", + "query": "SELECT 1", + "cube_elements": [ + "default.number_of_account_types", + "default.account_type", + ], + "name": "default.cubes_shouldnt_have_queries", + }, + ) + assert response.status_code == 422 + data = response.json() + assert data["detail"] == [ + { + "loc": ["body", "metrics"], + "msg": "field required", + "type": "value_error.missing", + }, + { + "loc": ["body", "dimensions"], + "msg": "field required", + "type": "value_error.missing", + }, + ] + + # Check that creating a cube with no cube elements fails appropriately + response = client_with_examples.post( + "/nodes/cube/", + json={ + "metrics": ["default.account_type"], + "dimensions": ["default.account_type.account_type_name"], + "description": "A cube of number of accounts grouped by account type", + "mode": "published", + "name": "default.cubes_must_have_elements", + }, + ) + assert response.status_code == 422 + data = response.json() + assert data == { + "message": "Node default.account_type of type dimension cannot be added to a cube. " + "Did you mean to add a dimension attribute?", + "errors": [], + "warnings": [], + } + + # Check that creating a cube with incompatible nodes fails appropriately + response = client_with_examples.post( + "/nodes/cube/", + json={ + "metrics": ["default.number_of_account_types"], + "dimensions": ["default.payment_type.payment_type_name"], + "description": "", + "mode": "published", + "name": "default.cubes_cant_use_source_nodes", + }, + ) + assert response.status_code == 422 + data = response.json() + assert data == { + "message": "The dimension attribute `default.payment_type.payment_type_name` " + "is not available on every metric and thus cannot be included.", + "errors": [], + "warnings": [], + } + + # Check that creating a cube with no metric nodes fails appropriately + response = client_with_examples.post( + "/nodes/cube/", + json={ + "metrics": [], + "dimensions": ["default.account_type.account_type_name"], + "description": "", + "mode": "published", + "name": "default.cubes_must_have_metrics", + }, + ) + assert response.status_code == 422 + data = response.json() + assert data == { + "message": "At least one metric is required", + "errors": [], + "warnings": [], + } + + # Check that creating a cube with no dimension nodes fails appropriately + response = client_with_examples.post( + "/nodes/cube/", + json={ + "metrics": ["default.number_of_account_types"], + "dimensions": [], + "description": "A cube of number of accounts grouped by account type", + "mode": "published", + "name": "default.cubes_must_have_dimensions", + }, + ) + assert response.status_code == 422 + data = response.json() + assert data == { + "message": "At least one dimension is required", + "errors": [], + "warnings": [], + } + + +def test_raise_on_cube_with_multiple_catalogs( + client_with_examples: TestClient, +) -> None: + """ + Test raising when creating a cube with multiple catalogs + """ + # Create a cube + response = client_with_examples.post( + "/nodes/cube/", + json={ + "metrics": ["default.number_of_account_types", "basic.num_comments"], + "dimensions": ["default.account_type.account_type_name"], + "description": "multicatalog cube's raise an error", + "mode": "published", + "name": "default.multicatalog", + }, + ) + assert not response.ok + data = response.json() + assert "Metrics and dimensions cannot be from multiple catalogs" in data["message"] + + +@pytest.fixture +def client_with_repairs_cube(client_with_query_service: TestClient): + """ + Adds a repairs cube with a new double total repair cost metric to the test client + """ + metrics_list = [ + "default.discounted_orders_rate", + "default.num_repair_orders", + "default.avg_repair_price", + "default.total_repair_cost", + "default.total_repair_order_discounts", + ] + + # Metric that doubles the total repair cost to test the sum(x) + sum(y) scenario + client_with_query_service.post( + "/nodes/metric/", + json={ + "description": "Double total repair cost", + "query": ( + "SELECT sum(price) + sum(price) as default_DOT_double_total_repair_cost " + "FROM default.repair_order_details" + ), + "mode": "published", + "name": "default.double_total_repair_cost", + }, + ) + # Should succeed + response = client_with_query_service.post( + "/nodes/cube/", + json={ + "metrics": metrics_list + ["default.double_total_repair_cost"], + "dimensions": [ + "default.hard_hat.country", + "default.hard_hat.postal_code", + "default.hard_hat.city", + "default.hard_hat.state", + "default.dispatcher.company_name", + "default.municipality_dim.local_region", + ], + "filters": ["default.hard_hat.state='AZ'"], + "description": "Cube of various metrics related to repairs", + "mode": "published", + "name": "default.repairs_cube", + }, + ) + assert response.status_code == 201 + return client_with_query_service + + +@pytest.fixture +def repair_orders_cube_measures() -> Dict: + """ + Fixture for repair orders cube metrics to measures mapping. + """ + return { + "default_DOT_avg_repair_price": { + "combiner": "sum(price_sum) / count(price_count)", + "measures": [ + { + "agg": "count", + "field_name": "m2_default_DOT_avg_repair_price_price_count", + "name": "price_count", + "type": "bigint", + }, + { + "agg": "sum", + "field_name": "m2_default_DOT_avg_repair_price_price_sum", + "name": "price_sum", + "type": "double", + }, + ], + "metric": "default_DOT_avg_repair_price", + }, + "default_DOT_discounted_orders_rate": { + "combiner": "sum(discount_sum) / " "count(placeholder_count)", + "measures": [ + { + "agg": "sum", + "field_name": "m0_default_DOT_discounted_orders_rate_discount_sum", + "name": "discount_sum", + "type": "bigint", + }, + { + "agg": "count", + "field_name": "m0_default_DOT_discounted_orders_rate_placeholder_count", + "name": "placeholder_count", + "type": "bigint", + }, + ], + "metric": "default_DOT_discounted_orders_rate", + }, + "default_DOT_double_total_repair_cost": { + "combiner": "sum(price_sum) + " "sum(price_sum)", + "measures": [ + { + "agg": "sum", + "field_name": "m5_default_DOT_double_total_repair_cost_price_sum", + "name": "price_sum", + "type": "double", + }, + { + "agg": "sum", + "field_name": "m5_default_DOT_double_total_repair_cost_price_sum", + "name": "price_sum", + "type": "double", + }, + ], + "metric": "default_DOT_double_total_repair_cost", + }, + "default_DOT_num_repair_orders": { + "combiner": "count(repair_order_id_count)", + "measures": [ + { + "agg": "count", + "field_name": "m1_default_DOT_num_repair_orders_repair_order_id_count", + "name": "repair_order_id_count", + "type": "bigint", + }, + ], + "metric": "default_DOT_num_repair_orders", + }, + "default_DOT_total_repair_cost": { + "combiner": "sum(price_sum)", + "measures": [ + { + "agg": "sum", + "field_name": "m3_default_DOT_total_repair_cost_price_sum", + "name": "price_sum", + "type": "double", + }, + ], + "metric": "default_DOT_total_repair_cost", + }, + "default_DOT_total_repair_order_discounts": { + "combiner": "sum(price_discount_sum)", + "measures": [ + { + "agg": "sum", + "field_name": "m4_default_DOT_total_repair_order_discounts_price_discount_sum", + "name": "price_discount_sum", + "type": "double", + }, + ], + "metric": "default_DOT_total_repair_order_discounts", + }, + } + + +def test_invalid_cube(client_with_examples: TestClient): + """ + Test that creating a cube without valid dimensions fails + """ + metrics_list = [ + "default.discounted_orders_rate", + "default.num_repair_orders", + "default.avg_repair_price", + "default.total_repair_cost", + "default.total_repair_order_discounts", + ] + # Should fail because dimension attribute isn't available + response = client_with_examples.post( + "/nodes/cube/", + json={ + "metrics": metrics_list, + "dimensions": [ + "default.contractor.company_name", + ], + "description": "Cube of various metrics related to repairs", + "mode": "published", + "name": "default.repairs_cube", + }, + ) + assert response.json()["message"] == ( + "The dimension attribute `default.contractor.company_name` " + "is not available on every metric and thus cannot be included." + ) + + +def test_create_cube( # pylint: disable=redefined-outer-name + client_with_repairs_cube: TestClient, + repair_orders_cube_measures, +): + """ + Tests cube creation and the generated cube SQL + """ + response = client_with_repairs_cube.get("/nodes/default.repairs_cube/") + results = response.json() + + assert results["name"] == "default.repairs_cube" + assert results["display_name"] == "Default: Repairs Cube" + assert results["description"] == "Cube of various metrics related to repairs" + + default_materialization = response.json()["materializations"][0] + assert default_materialization["job"] == "DefaultCubeMaterialization" + assert default_materialization["name"] == "default" + assert default_materialization["schedule"] == "@daily" + assert default_materialization["config"]["partitions"] == [] + assert default_materialization["config"]["upstream_tables"] == [ + "default.roads.dispatchers", + "default.roads.hard_hats", + "default.roads.municipality", + "default.roads.municipality_municipality_type", + "default.roads.municipality_type", + "default.roads.repair_order_details", + "default.roads.repair_orders", + ] + assert default_materialization["config"]["dimensions"] == [ + "city", + "company_name", + "country", + "local_region", + "postal_code", + "state", + ] + assert default_materialization["config"]["measures"] == repair_orders_cube_measures + + expected_query = """ + WITH + m0_default_DOT_discounted_orders_rate AS (SELECT default_DOT_dispatcher.company_name, + default_DOT_hard_hat.city, + default_DOT_hard_hat.country, + default_DOT_hard_hat.postal_code, + default_DOT_hard_hat.state, + default_DOT_municipality_dim.local_region, + CAST(sum(if(default_DOT_repair_order_details.discount > 0.0, 1, 0)) AS DOUBLE) / count(*) AS default_DOT_discounted_orders_rate + FROM roads.repair_order_details AS default_DOT_repair_order_details LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders) + AS default_DOT_repair_order ON default_DOT_repair_order_details.repair_order_id = default_DOT_repair_order.repair_order_id + LEFT OUTER JOIN (SELECT default_DOT_dispatchers.company_name, + default_DOT_dispatchers.dispatcher_id + FROM roads.dispatchers AS default_DOT_dispatchers) + AS default_DOT_dispatcher ON default_DOT_repair_order.dispatcher_id = default_DOT_dispatcher.dispatcher_id + LEFT OUTER JOIN (SELECT default_DOT_hard_hats.city, + default_DOT_hard_hats.country, + default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.postal_code, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats) + AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + LEFT OUTER JOIN (SELECT default_DOT_municipality.local_region, + default_DOT_municipality.municipality_id AS municipality_id + FROM roads.municipality AS default_DOT_municipality LEFT JOIN roads.municipality_municipality_type AS default_DOT_municipality_municipality_type ON default_DOT_municipality.municipality_id = default_DOT_municipality_municipality_type.municipality_id + LEFT JOIN roads.municipality_type AS default_DOT_municipality_type ON default_DOT_municipality_municipality_type.municipality_type_id = default_DOT_municipality_type.municipality_type_desc) + AS default_DOT_municipality_dim ON default_DOT_repair_order.municipality_id = default_DOT_municipality_dim.municipality_id + WHERE default_DOT_hard_hat.state = 'AZ' + GROUP BY default_DOT_hard_hat.country, default_DOT_hard_hat.postal_code, default_DOT_hard_hat.city, default_DOT_hard_hat.state, default_DOT_dispatcher.company_name, default_DOT_municipality_dim.local_region + ), + m1_default_DOT_num_repair_orders AS (SELECT default_DOT_dispatcher.company_name, + default_DOT_hard_hat.city, + default_DOT_hard_hat.country, + default_DOT_hard_hat.postal_code, + default_DOT_hard_hat.state, + default_DOT_municipality_dim.local_region, + count(default_DOT_repair_orders.repair_order_id) default_DOT_num_repair_orders + FROM roads.repair_orders AS default_DOT_repair_orders LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders) + AS default_DOT_repair_order ON default_DOT_repair_orders.repair_order_id = default_DOT_repair_order.repair_order_id + LEFT OUTER JOIN (SELECT default_DOT_dispatchers.company_name, + default_DOT_dispatchers.dispatcher_id + FROM roads.dispatchers AS default_DOT_dispatchers) + AS default_DOT_dispatcher ON default_DOT_repair_order.dispatcher_id = default_DOT_dispatcher.dispatcher_id + LEFT OUTER JOIN (SELECT default_DOT_hard_hats.city, + default_DOT_hard_hats.country, + default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.postal_code, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats) + AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + LEFT OUTER JOIN (SELECT default_DOT_municipality.local_region, + default_DOT_municipality.municipality_id AS municipality_id + FROM roads.municipality AS default_DOT_municipality LEFT JOIN roads.municipality_municipality_type AS default_DOT_municipality_municipality_type ON default_DOT_municipality.municipality_id = default_DOT_municipality_municipality_type.municipality_id + LEFT JOIN roads.municipality_type AS default_DOT_municipality_type ON default_DOT_municipality_municipality_type.municipality_type_id = default_DOT_municipality_type.municipality_type_desc) + AS default_DOT_municipality_dim ON default_DOT_repair_order.municipality_id = default_DOT_municipality_dim.municipality_id + WHERE default_DOT_hard_hat.state = 'AZ' + GROUP BY default_DOT_hard_hat.country, default_DOT_hard_hat.postal_code, default_DOT_hard_hat.city, default_DOT_hard_hat.state, default_DOT_dispatcher.company_name, default_DOT_municipality_dim.local_region + ), + m2_default_DOT_avg_repair_price AS (SELECT default_DOT_dispatcher.company_name, + default_DOT_hard_hat.city, + default_DOT_hard_hat.country, + default_DOT_hard_hat.postal_code, + default_DOT_hard_hat.state, + default_DOT_municipality_dim.local_region, + avg(default_DOT_repair_order_details.price) AS default_DOT_avg_repair_price + FROM roads.repair_order_details AS default_DOT_repair_order_details LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders) + AS default_DOT_repair_order ON default_DOT_repair_order_details.repair_order_id = default_DOT_repair_order.repair_order_id + LEFT OUTER JOIN (SELECT default_DOT_dispatchers.company_name, + default_DOT_dispatchers.dispatcher_id + FROM roads.dispatchers AS default_DOT_dispatchers) + AS default_DOT_dispatcher ON default_DOT_repair_order.dispatcher_id = default_DOT_dispatcher.dispatcher_id + LEFT OUTER JOIN (SELECT default_DOT_hard_hats.city, + default_DOT_hard_hats.country, + default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.postal_code, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats) + AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + LEFT OUTER JOIN (SELECT default_DOT_municipality.local_region, + default_DOT_municipality.municipality_id AS municipality_id + FROM roads.municipality AS default_DOT_municipality LEFT JOIN roads.municipality_municipality_type AS default_DOT_municipality_municipality_type ON default_DOT_municipality.municipality_id = default_DOT_municipality_municipality_type.municipality_id + LEFT JOIN roads.municipality_type AS default_DOT_municipality_type ON default_DOT_municipality_municipality_type.municipality_type_id = default_DOT_municipality_type.municipality_type_desc) + AS default_DOT_municipality_dim ON default_DOT_repair_order.municipality_id = default_DOT_municipality_dim.municipality_id + WHERE default_DOT_hard_hat.state = 'AZ' + GROUP BY default_DOT_hard_hat.country, default_DOT_hard_hat.postal_code, default_DOT_hard_hat.city, default_DOT_hard_hat.state, default_DOT_dispatcher.company_name, default_DOT_municipality_dim.local_region + ), + m3_default_DOT_total_repair_cost AS (SELECT default_DOT_dispatcher.company_name, + default_DOT_hard_hat.city, + default_DOT_hard_hat.country, + default_DOT_hard_hat.postal_code, + default_DOT_hard_hat.state, + default_DOT_municipality_dim.local_region, + sum(default_DOT_repair_order_details.price) default_DOT_total_repair_cost + FROM roads.repair_order_details AS default_DOT_repair_order_details LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders) + AS default_DOT_repair_order ON default_DOT_repair_order_details.repair_order_id = default_DOT_repair_order.repair_order_id + LEFT OUTER JOIN (SELECT default_DOT_dispatchers.company_name, + default_DOT_dispatchers.dispatcher_id + FROM roads.dispatchers AS default_DOT_dispatchers) + AS default_DOT_dispatcher ON default_DOT_repair_order.dispatcher_id = default_DOT_dispatcher.dispatcher_id + LEFT OUTER JOIN (SELECT default_DOT_hard_hats.city, + default_DOT_hard_hats.country, + default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.postal_code, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats) + AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + LEFT OUTER JOIN (SELECT default_DOT_municipality.local_region, + default_DOT_municipality.municipality_id AS municipality_id + FROM roads.municipality AS default_DOT_municipality LEFT JOIN roads.municipality_municipality_type AS default_DOT_municipality_municipality_type ON default_DOT_municipality.municipality_id = default_DOT_municipality_municipality_type.municipality_id + LEFT JOIN roads.municipality_type AS default_DOT_municipality_type ON default_DOT_municipality_municipality_type.municipality_type_id = default_DOT_municipality_type.municipality_type_desc) + AS default_DOT_municipality_dim ON default_DOT_repair_order.municipality_id = default_DOT_municipality_dim.municipality_id + WHERE default_DOT_hard_hat.state = 'AZ' + GROUP BY default_DOT_hard_hat.country, default_DOT_hard_hat.postal_code, default_DOT_hard_hat.city, default_DOT_hard_hat.state, default_DOT_dispatcher.company_name, default_DOT_municipality_dim.local_region + ), + m4_default_DOT_total_repair_order_discounts AS (SELECT default_DOT_dispatcher.company_name, + default_DOT_hard_hat.city, + default_DOT_hard_hat.country, + default_DOT_hard_hat.postal_code, + default_DOT_hard_hat.state, + default_DOT_municipality_dim.local_region, + sum(default_DOT_repair_order_details.price * default_DOT_repair_order_details.discount) default_DOT_total_repair_order_discounts + FROM roads.repair_order_details AS default_DOT_repair_order_details LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders) + AS default_DOT_repair_order ON default_DOT_repair_order_details.repair_order_id = default_DOT_repair_order.repair_order_id + LEFT OUTER JOIN (SELECT default_DOT_dispatchers.company_name, + default_DOT_dispatchers.dispatcher_id + FROM roads.dispatchers AS default_DOT_dispatchers) + AS default_DOT_dispatcher ON default_DOT_repair_order.dispatcher_id = default_DOT_dispatcher.dispatcher_id + LEFT OUTER JOIN (SELECT default_DOT_hard_hats.city, + default_DOT_hard_hats.country, + default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.postal_code, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats) + AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + LEFT OUTER JOIN (SELECT default_DOT_municipality.local_region, + default_DOT_municipality.municipality_id AS municipality_id + FROM roads.municipality AS default_DOT_municipality LEFT JOIN roads.municipality_municipality_type AS default_DOT_municipality_municipality_type ON default_DOT_municipality.municipality_id = default_DOT_municipality_municipality_type.municipality_id + LEFT JOIN roads.municipality_type AS default_DOT_municipality_type ON default_DOT_municipality_municipality_type.municipality_type_id = default_DOT_municipality_type.municipality_type_desc) + AS default_DOT_municipality_dim ON default_DOT_repair_order.municipality_id = default_DOT_municipality_dim.municipality_id + WHERE default_DOT_hard_hat.state = 'AZ' + GROUP BY default_DOT_hard_hat.country, default_DOT_hard_hat.postal_code, default_DOT_hard_hat.city, default_DOT_hard_hat.state, default_DOT_dispatcher.company_name, default_DOT_municipality_dim.local_region + ), + m5_default_DOT_double_total_repair_cost AS (SELECT default_DOT_dispatcher.company_name, + default_DOT_hard_hat.city, + default_DOT_hard_hat.country, + default_DOT_hard_hat.postal_code, + default_DOT_hard_hat.state, + default_DOT_municipality_dim.local_region, + sum(default_DOT_repair_order_details.price) + sum(default_DOT_repair_order_details.price) AS default_DOT_double_total_repair_cost + FROM roads.repair_order_details AS default_DOT_repair_order_details LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders) + AS default_DOT_repair_order ON default_DOT_repair_order_details.repair_order_id = default_DOT_repair_order.repair_order_id + LEFT OUTER JOIN (SELECT default_DOT_dispatchers.company_name, + default_DOT_dispatchers.dispatcher_id + FROM roads.dispatchers AS default_DOT_dispatchers) + AS default_DOT_dispatcher ON default_DOT_repair_order.dispatcher_id = default_DOT_dispatcher.dispatcher_id + LEFT OUTER JOIN (SELECT default_DOT_hard_hats.city, + default_DOT_hard_hats.country, + default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.postal_code, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats) + AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + LEFT OUTER JOIN (SELECT default_DOT_municipality.local_region, + default_DOT_municipality.municipality_id AS municipality_id + FROM roads.municipality AS default_DOT_municipality LEFT JOIN roads.municipality_municipality_type AS default_DOT_municipality_municipality_type ON default_DOT_municipality.municipality_id = default_DOT_municipality_municipality_type.municipality_id + LEFT JOIN roads.municipality_type AS default_DOT_municipality_type ON default_DOT_municipality_municipality_type.municipality_type_id = default_DOT_municipality_type.municipality_type_desc) + AS default_DOT_municipality_dim ON default_DOT_repair_order.municipality_id = default_DOT_municipality_dim.municipality_id + WHERE default_DOT_hard_hat.state = 'AZ' + GROUP BY default_DOT_hard_hat.country, default_DOT_hard_hat.postal_code, default_DOT_hard_hat.city, default_DOT_hard_hat.state, default_DOT_dispatcher.company_name, default_DOT_municipality_dim.local_region + )SELECT m0_default_DOT_discounted_orders_rate.default_DOT_discounted_orders_rate, + m1_default_DOT_num_repair_orders.default_DOT_num_repair_orders, + m2_default_DOT_avg_repair_price.default_DOT_avg_repair_price, + m3_default_DOT_total_repair_cost.default_DOT_total_repair_cost, + m4_default_DOT_total_repair_order_discounts.default_DOT_total_repair_order_discounts, + m5_default_DOT_double_total_repair_cost.default_DOT_double_total_repair_cost, + COALESCE(m0_default_DOT_discounted_orders_rate.company_name, m1_default_DOT_num_repair_orders.company_name, m2_default_DOT_avg_repair_price.company_name, m3_default_DOT_total_repair_cost.company_name, m4_default_DOT_total_repair_order_discounts.company_name, m5_default_DOT_double_total_repair_cost.company_name) company_name, + COALESCE(m0_default_DOT_discounted_orders_rate.city, m1_default_DOT_num_repair_orders.city, m2_default_DOT_avg_repair_price.city, m3_default_DOT_total_repair_cost.city, m4_default_DOT_total_repair_order_discounts.city, m5_default_DOT_double_total_repair_cost.city) city, + COALESCE(m0_default_DOT_discounted_orders_rate.country, m1_default_DOT_num_repair_orders.country, m2_default_DOT_avg_repair_price.country, m3_default_DOT_total_repair_cost.country, m4_default_DOT_total_repair_order_discounts.country, m5_default_DOT_double_total_repair_cost.country) country, + COALESCE(m0_default_DOT_discounted_orders_rate.postal_code, m1_default_DOT_num_repair_orders.postal_code, m2_default_DOT_avg_repair_price.postal_code, m3_default_DOT_total_repair_cost.postal_code, m4_default_DOT_total_repair_order_discounts.postal_code, m5_default_DOT_double_total_repair_cost.postal_code) postal_code, + COALESCE(m0_default_DOT_discounted_orders_rate.state, m1_default_DOT_num_repair_orders.state, m2_default_DOT_avg_repair_price.state, m3_default_DOT_total_repair_cost.state, m4_default_DOT_total_repair_order_discounts.state, m5_default_DOT_double_total_repair_cost.state) state, + COALESCE(m0_default_DOT_discounted_orders_rate.local_region, m1_default_DOT_num_repair_orders.local_region, m2_default_DOT_avg_repair_price.local_region, m3_default_DOT_total_repair_cost.local_region, m4_default_DOT_total_repair_order_discounts.local_region, m5_default_DOT_double_total_repair_cost.local_region) local_region + FROM m0_default_DOT_discounted_orders_rate FULL OUTER JOIN m1_default_DOT_num_repair_orders ON m0_default_DOT_discounted_orders_rate.company_name = m1_default_DOT_num_repair_orders.company_name AND m0_default_DOT_discounted_orders_rate.city = m1_default_DOT_num_repair_orders.city AND m0_default_DOT_discounted_orders_rate.country = m1_default_DOT_num_repair_orders.country AND m0_default_DOT_discounted_orders_rate.postal_code = m1_default_DOT_num_repair_orders.postal_code AND m0_default_DOT_discounted_orders_rate.state = m1_default_DOT_num_repair_orders.state AND m0_default_DOT_discounted_orders_rate.local_region = m1_default_DOT_num_repair_orders.local_region + FULL OUTER JOIN m2_default_DOT_avg_repair_price ON m0_default_DOT_discounted_orders_rate.company_name = m2_default_DOT_avg_repair_price.company_name AND m0_default_DOT_discounted_orders_rate.city = m2_default_DOT_avg_repair_price.city AND m0_default_DOT_discounted_orders_rate.country = m2_default_DOT_avg_repair_price.country AND m0_default_DOT_discounted_orders_rate.postal_code = m2_default_DOT_avg_repair_price.postal_code AND m0_default_DOT_discounted_orders_rate.state = m2_default_DOT_avg_repair_price.state AND m0_default_DOT_discounted_orders_rate.local_region = m2_default_DOT_avg_repair_price.local_region + FULL OUTER JOIN m3_default_DOT_total_repair_cost ON m0_default_DOT_discounted_orders_rate.company_name = m3_default_DOT_total_repair_cost.company_name AND m0_default_DOT_discounted_orders_rate.city = m3_default_DOT_total_repair_cost.city AND m0_default_DOT_discounted_orders_rate.country = m3_default_DOT_total_repair_cost.country AND m0_default_DOT_discounted_orders_rate.postal_code = m3_default_DOT_total_repair_cost.postal_code AND m0_default_DOT_discounted_orders_rate.state = m3_default_DOT_total_repair_cost.state AND m0_default_DOT_discounted_orders_rate.local_region = m3_default_DOT_total_repair_cost.local_region + FULL OUTER JOIN m4_default_DOT_total_repair_order_discounts ON m0_default_DOT_discounted_orders_rate.company_name = m4_default_DOT_total_repair_order_discounts.company_name AND m0_default_DOT_discounted_orders_rate.city = m4_default_DOT_total_repair_order_discounts.city AND m0_default_DOT_discounted_orders_rate.country = m4_default_DOT_total_repair_order_discounts.country AND m0_default_DOT_discounted_orders_rate.postal_code = m4_default_DOT_total_repair_order_discounts.postal_code AND m0_default_DOT_discounted_orders_rate.state = m4_default_DOT_total_repair_order_discounts.state AND m0_default_DOT_discounted_orders_rate.local_region = m4_default_DOT_total_repair_order_discounts.local_region + FULL OUTER JOIN m5_default_DOT_double_total_repair_cost ON m0_default_DOT_discounted_orders_rate.company_name = m5_default_DOT_double_total_repair_cost.company_name AND m0_default_DOT_discounted_orders_rate.city = m5_default_DOT_double_total_repair_cost.city AND m0_default_DOT_discounted_orders_rate.country = m5_default_DOT_double_total_repair_cost.country AND m0_default_DOT_discounted_orders_rate.postal_code = m5_default_DOT_double_total_repair_cost.postal_code AND m0_default_DOT_discounted_orders_rate.state = m5_default_DOT_double_total_repair_cost.state AND m0_default_DOT_discounted_orders_rate.local_region = m5_default_DOT_double_total_repair_cost.local_region + """ + assert compare_query_strings(results["query"], expected_query) + + +def test_cube_materialization_sql_and_measures( + client_with_repairs_cube: TestClient, # pylint: disable=redefined-outer-name + repair_orders_cube_measures, # pylint: disable=redefined-outer-name +): + """ + Verifies a cube's materialization SQL + measures + """ + response = client_with_repairs_cube.get("/cubes/default.repairs_cube/") + data = response.json() + assert data["cube_elements"] == [ + { + "name": "default_DOT_discounted_orders_rate", + "node_name": "default.discounted_orders_rate", + "type": "metric", + }, + { + "name": "default_DOT_num_repair_orders", + "node_name": "default.num_repair_orders", + "type": "metric", + }, + { + "name": "default_DOT_avg_repair_price", + "node_name": "default.avg_repair_price", + "type": "metric", + }, + { + "name": "default_DOT_total_repair_cost", + "node_name": "default.total_repair_cost", + "type": "metric", + }, + { + "name": "default_DOT_total_repair_order_discounts", + "node_name": "default.total_repair_order_discounts", + "type": "metric", + }, + { + "name": "default_DOT_double_total_repair_cost", + "node_name": "default.double_total_repair_cost", + "type": "metric", + }, + {"name": "country", "node_name": "default.hard_hat", "type": "dimension"}, + {"name": "postal_code", "node_name": "default.hard_hat", "type": "dimension"}, + {"name": "city", "node_name": "default.hard_hat", "type": "dimension"}, + {"name": "state", "node_name": "default.hard_hat", "type": "dimension"}, + { + "name": "company_name", + "node_name": "default.dispatcher", + "type": "dimension", + }, + { + "name": "local_region", + "node_name": "default.municipality_dim", + "type": "dimension", + }, + ] + expected_materialization_query = """ + WITH + m0_default_DOT_discounted_orders_rate AS (SELECT default_DOT_hard_hat.city, + default_DOT_dispatcher.company_name, + default_DOT_hard_hat.state, + count(*) placeholder_count, + default_DOT_municipality_dim.local_region, + default_DOT_hard_hat.postal_code, + sum(if(default_DOT_repair_order_details.discount > 0.0, 1, 0)) discount_sum, + default_DOT_hard_hat.country + FROM roads.repair_order_details AS default_DOT_repair_order_details LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders) + AS default_DOT_repair_order ON default_DOT_repair_order_details.repair_order_id = default_DOT_repair_order.repair_order_id + LEFT OUTER JOIN (SELECT default_DOT_dispatchers.company_name, + default_DOT_dispatchers.dispatcher_id + FROM roads.dispatchers AS default_DOT_dispatchers) + AS default_DOT_dispatcher ON default_DOT_repair_order.dispatcher_id = default_DOT_dispatcher.dispatcher_id + LEFT OUTER JOIN (SELECT default_DOT_hard_hats.city, + default_DOT_hard_hats.country, + default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.postal_code, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats) + AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + LEFT OUTER JOIN (SELECT default_DOT_municipality.local_region, + default_DOT_municipality.municipality_id AS municipality_id + FROM roads.municipality AS default_DOT_municipality LEFT JOIN roads.municipality_municipality_type AS default_DOT_municipality_municipality_type ON default_DOT_municipality.municipality_id = default_DOT_municipality_municipality_type.municipality_id + LEFT JOIN roads.municipality_type AS default_DOT_municipality_type ON default_DOT_municipality_municipality_type.municipality_type_id = default_DOT_municipality_type.municipality_type_desc) + AS default_DOT_municipality_dim ON default_DOT_repair_order.municipality_id = default_DOT_municipality_dim.municipality_id + WHERE default_DOT_hard_hat.state = 'AZ' + GROUP BY default_DOT_hard_hat.country, default_DOT_hard_hat.postal_code, default_DOT_hard_hat.city, default_DOT_hard_hat.state, default_DOT_dispatcher.company_name, default_DOT_municipality_dim.local_region + ), + m1_default_DOT_num_repair_orders AS (SELECT default_DOT_hard_hat.city, + default_DOT_dispatcher.company_name, + default_DOT_hard_hat.state, + default_DOT_municipality_dim.local_region, + default_DOT_hard_hat.postal_code, + default_DOT_hard_hat.country, + count(default_DOT_repair_orders.repair_order_id) repair_order_id_count + FROM roads.repair_orders AS default_DOT_repair_orders LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders) + AS default_DOT_repair_order ON default_DOT_repair_orders.repair_order_id = default_DOT_repair_order.repair_order_id + LEFT OUTER JOIN (SELECT default_DOT_dispatchers.company_name, + default_DOT_dispatchers.dispatcher_id + FROM roads.dispatchers AS default_DOT_dispatchers) + AS default_DOT_dispatcher ON default_DOT_repair_order.dispatcher_id = default_DOT_dispatcher.dispatcher_id + LEFT OUTER JOIN (SELECT default_DOT_hard_hats.city, + default_DOT_hard_hats.country, + default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.postal_code, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats) + AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + LEFT OUTER JOIN (SELECT default_DOT_municipality.local_region, + default_DOT_municipality.municipality_id AS municipality_id + FROM roads.municipality AS default_DOT_municipality LEFT JOIN roads.municipality_municipality_type AS default_DOT_municipality_municipality_type ON default_DOT_municipality.municipality_id = default_DOT_municipality_municipality_type.municipality_id + LEFT JOIN roads.municipality_type AS default_DOT_municipality_type ON default_DOT_municipality_municipality_type.municipality_type_id = default_DOT_municipality_type.municipality_type_desc) + AS default_DOT_municipality_dim ON default_DOT_repair_order.municipality_id = default_DOT_municipality_dim.municipality_id + WHERE default_DOT_hard_hat.state = 'AZ' + GROUP BY default_DOT_hard_hat.country, default_DOT_hard_hat.postal_code, default_DOT_hard_hat.city, default_DOT_hard_hat.state, default_DOT_dispatcher.company_name, default_DOT_municipality_dim.local_region + ), + m2_default_DOT_avg_repair_price AS (SELECT default_DOT_hard_hat.city, + default_DOT_dispatcher.company_name, + default_DOT_hard_hat.state, + sum(default_DOT_repair_order_details.price) price_sum, + default_DOT_municipality_dim.local_region, + count(default_DOT_repair_order_details.price) price_count, + default_DOT_hard_hat.postal_code, + default_DOT_hard_hat.country + FROM roads.repair_order_details AS default_DOT_repair_order_details LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders) + AS default_DOT_repair_order ON default_DOT_repair_order_details.repair_order_id = default_DOT_repair_order.repair_order_id + LEFT OUTER JOIN (SELECT default_DOT_dispatchers.company_name, + default_DOT_dispatchers.dispatcher_id + FROM roads.dispatchers AS default_DOT_dispatchers) + AS default_DOT_dispatcher ON default_DOT_repair_order.dispatcher_id = default_DOT_dispatcher.dispatcher_id + LEFT OUTER JOIN (SELECT default_DOT_hard_hats.city, + default_DOT_hard_hats.country, + default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.postal_code, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats) + AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + LEFT OUTER JOIN (SELECT default_DOT_municipality.local_region, + default_DOT_municipality.municipality_id AS municipality_id + FROM roads.municipality AS default_DOT_municipality LEFT JOIN roads.municipality_municipality_type AS default_DOT_municipality_municipality_type ON default_DOT_municipality.municipality_id = default_DOT_municipality_municipality_type.municipality_id + LEFT JOIN roads.municipality_type AS default_DOT_municipality_type ON default_DOT_municipality_municipality_type.municipality_type_id = default_DOT_municipality_type.municipality_type_desc) + AS default_DOT_municipality_dim ON default_DOT_repair_order.municipality_id = default_DOT_municipality_dim.municipality_id + WHERE default_DOT_hard_hat.state = 'AZ' + GROUP BY default_DOT_hard_hat.country, default_DOT_hard_hat.postal_code, default_DOT_hard_hat.city, default_DOT_hard_hat.state, default_DOT_dispatcher.company_name, default_DOT_municipality_dim.local_region + ), + m3_default_DOT_total_repair_cost AS (SELECT default_DOT_hard_hat.city, + default_DOT_dispatcher.company_name, + default_DOT_hard_hat.state, + sum(default_DOT_repair_order_details.price) price_sum, + default_DOT_municipality_dim.local_region, + default_DOT_hard_hat.postal_code, + default_DOT_hard_hat.country + FROM roads.repair_order_details AS default_DOT_repair_order_details LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders) + AS default_DOT_repair_order ON default_DOT_repair_order_details.repair_order_id = default_DOT_repair_order.repair_order_id + LEFT OUTER JOIN (SELECT default_DOT_dispatchers.company_name, + default_DOT_dispatchers.dispatcher_id + FROM roads.dispatchers AS default_DOT_dispatchers) + AS default_DOT_dispatcher ON default_DOT_repair_order.dispatcher_id = default_DOT_dispatcher.dispatcher_id + LEFT OUTER JOIN (SELECT default_DOT_hard_hats.city, + default_DOT_hard_hats.country, + default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.postal_code, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats) + AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + LEFT OUTER JOIN (SELECT default_DOT_municipality.local_region, + default_DOT_municipality.municipality_id AS municipality_id + FROM roads.municipality AS default_DOT_municipality LEFT JOIN roads.municipality_municipality_type AS default_DOT_municipality_municipality_type ON default_DOT_municipality.municipality_id = default_DOT_municipality_municipality_type.municipality_id + LEFT JOIN roads.municipality_type AS default_DOT_municipality_type ON default_DOT_municipality_municipality_type.municipality_type_id = default_DOT_municipality_type.municipality_type_desc) + AS default_DOT_municipality_dim ON default_DOT_repair_order.municipality_id = default_DOT_municipality_dim.municipality_id + WHERE default_DOT_hard_hat.state = 'AZ' + GROUP BY default_DOT_hard_hat.country, default_DOT_hard_hat.postal_code, default_DOT_hard_hat.city, default_DOT_hard_hat.state, default_DOT_dispatcher.company_name, default_DOT_municipality_dim.local_region + ), + m4_default_DOT_total_repair_order_discounts AS (SELECT default_DOT_hard_hat.city, + default_DOT_dispatcher.company_name, + default_DOT_hard_hat.state, + sum(default_DOT_repair_order_details.price * default_DOT_repair_order_details.discount) price_discount_sum, + default_DOT_municipality_dim.local_region, + default_DOT_hard_hat.postal_code, + default_DOT_hard_hat.country + FROM roads.repair_order_details AS default_DOT_repair_order_details LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders) + AS default_DOT_repair_order ON default_DOT_repair_order_details.repair_order_id = default_DOT_repair_order.repair_order_id + LEFT OUTER JOIN (SELECT default_DOT_dispatchers.company_name, + default_DOT_dispatchers.dispatcher_id + FROM roads.dispatchers AS default_DOT_dispatchers) + AS default_DOT_dispatcher ON default_DOT_repair_order.dispatcher_id = default_DOT_dispatcher.dispatcher_id + LEFT OUTER JOIN (SELECT default_DOT_hard_hats.city, + default_DOT_hard_hats.country, + default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.postal_code, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats) + AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + LEFT OUTER JOIN (SELECT default_DOT_municipality.local_region, + default_DOT_municipality.municipality_id AS municipality_id + FROM roads.municipality AS default_DOT_municipality LEFT JOIN roads.municipality_municipality_type AS default_DOT_municipality_municipality_type ON default_DOT_municipality.municipality_id = default_DOT_municipality_municipality_type.municipality_id + LEFT JOIN roads.municipality_type AS default_DOT_municipality_type ON default_DOT_municipality_municipality_type.municipality_type_id = default_DOT_municipality_type.municipality_type_desc) + AS default_DOT_municipality_dim ON default_DOT_repair_order.municipality_id = default_DOT_municipality_dim.municipality_id + WHERE default_DOT_hard_hat.state = 'AZ' + GROUP BY default_DOT_hard_hat.country, default_DOT_hard_hat.postal_code, default_DOT_hard_hat.city, default_DOT_hard_hat.state, default_DOT_dispatcher.company_name, default_DOT_municipality_dim.local_region + ), + m5_default_DOT_double_total_repair_cost AS (SELECT default_DOT_hard_hat.city, + default_DOT_dispatcher.company_name, + default_DOT_hard_hat.state, + sum(default_DOT_repair_order_details.price) price_sum, + default_DOT_municipality_dim.local_region, + default_DOT_hard_hat.postal_code, + default_DOT_hard_hat.country + FROM roads.repair_order_details AS default_DOT_repair_order_details LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders) + AS default_DOT_repair_order ON default_DOT_repair_order_details.repair_order_id = default_DOT_repair_order.repair_order_id + LEFT OUTER JOIN (SELECT default_DOT_dispatchers.company_name, + default_DOT_dispatchers.dispatcher_id + FROM roads.dispatchers AS default_DOT_dispatchers) + AS default_DOT_dispatcher ON default_DOT_repair_order.dispatcher_id = default_DOT_dispatcher.dispatcher_id + LEFT OUTER JOIN (SELECT default_DOT_hard_hats.city, + default_DOT_hard_hats.country, + default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.postal_code, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats) + AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + LEFT OUTER JOIN (SELECT default_DOT_municipality.local_region, + default_DOT_municipality.municipality_id AS municipality_id + FROM roads.municipality AS default_DOT_municipality LEFT JOIN roads.municipality_municipality_type AS default_DOT_municipality_municipality_type ON default_DOT_municipality.municipality_id = default_DOT_municipality_municipality_type.municipality_id + LEFT JOIN roads.municipality_type AS default_DOT_municipality_type ON default_DOT_municipality_municipality_type.municipality_type_id = default_DOT_municipality_type.municipality_type_desc) + AS default_DOT_municipality_dim ON default_DOT_repair_order.municipality_id = default_DOT_municipality_dim.municipality_id + WHERE default_DOT_hard_hat.state = 'AZ' + GROUP BY default_DOT_hard_hat.country, default_DOT_hard_hat.postal_code, default_DOT_hard_hat.city, default_DOT_hard_hat.state, default_DOT_dispatcher.company_name, default_DOT_municipality_dim.local_region + )SELECT m0_default_DOT_discounted_orders_rate.placeholder_count m0_default_DOT_discounted_orders_rate_placeholder_count, + m0_default_DOT_discounted_orders_rate.discount_sum m0_default_DOT_discounted_orders_rate_discount_sum, + m1_default_DOT_num_repair_orders.repair_order_id_count m1_default_DOT_num_repair_orders_repair_order_id_count, + m2_default_DOT_avg_repair_price.price_sum m2_default_DOT_avg_repair_price_price_sum, + m2_default_DOT_avg_repair_price.price_count m2_default_DOT_avg_repair_price_price_count, + m3_default_DOT_total_repair_cost.price_sum m3_default_DOT_total_repair_cost_price_sum, + m4_default_DOT_total_repair_order_discounts.price_discount_sum m4_default_DOT_total_repair_order_discounts_price_discount_sum, + m5_default_DOT_double_total_repair_cost.price_sum m5_default_DOT_double_total_repair_cost_price_sum, + COALESCE(m0_default_DOT_discounted_orders_rate.city, m1_default_DOT_num_repair_orders.city, m2_default_DOT_avg_repair_price.city, m3_default_DOT_total_repair_cost.city, m4_default_DOT_total_repair_order_discounts.city, m5_default_DOT_double_total_repair_cost.city) city, + COALESCE(m0_default_DOT_discounted_orders_rate.company_name, m1_default_DOT_num_repair_orders.company_name, m2_default_DOT_avg_repair_price.company_name, m3_default_DOT_total_repair_cost.company_name, m4_default_DOT_total_repair_order_discounts.company_name, m5_default_DOT_double_total_repair_cost.company_name) company_name, + COALESCE(m0_default_DOT_discounted_orders_rate.state, m1_default_DOT_num_repair_orders.state, m2_default_DOT_avg_repair_price.state, m3_default_DOT_total_repair_cost.state, m4_default_DOT_total_repair_order_discounts.state, m5_default_DOT_double_total_repair_cost.state) state, + COALESCE(m0_default_DOT_discounted_orders_rate.local_region, m1_default_DOT_num_repair_orders.local_region, m2_default_DOT_avg_repair_price.local_region, m3_default_DOT_total_repair_cost.local_region, m4_default_DOT_total_repair_order_discounts.local_region, m5_default_DOT_double_total_repair_cost.local_region) local_region, + COALESCE(m0_default_DOT_discounted_orders_rate.postal_code, m1_default_DOT_num_repair_orders.postal_code, m2_default_DOT_avg_repair_price.postal_code, m3_default_DOT_total_repair_cost.postal_code, m4_default_DOT_total_repair_order_discounts.postal_code, m5_default_DOT_double_total_repair_cost.postal_code) postal_code, + COALESCE(m0_default_DOT_discounted_orders_rate.country, m1_default_DOT_num_repair_orders.country, m2_default_DOT_avg_repair_price.country, m3_default_DOT_total_repair_cost.country, m4_default_DOT_total_repair_order_discounts.country, m5_default_DOT_double_total_repair_cost.country) country + FROM m0_default_DOT_discounted_orders_rate FULL OUTER JOIN m1_default_DOT_num_repair_orders ON m0_default_DOT_discounted_orders_rate.company_name = m1_default_DOT_num_repair_orders.company_name AND m0_default_DOT_discounted_orders_rate.city = m1_default_DOT_num_repair_orders.city AND m0_default_DOT_discounted_orders_rate.country = m1_default_DOT_num_repair_orders.country AND m0_default_DOT_discounted_orders_rate.postal_code = m1_default_DOT_num_repair_orders.postal_code AND m0_default_DOT_discounted_orders_rate.state = m1_default_DOT_num_repair_orders.state AND m0_default_DOT_discounted_orders_rate.local_region = m1_default_DOT_num_repair_orders.local_region + FULL OUTER JOIN m2_default_DOT_avg_repair_price ON m0_default_DOT_discounted_orders_rate.company_name = m2_default_DOT_avg_repair_price.company_name AND m0_default_DOT_discounted_orders_rate.city = m2_default_DOT_avg_repair_price.city AND m0_default_DOT_discounted_orders_rate.country = m2_default_DOT_avg_repair_price.country AND m0_default_DOT_discounted_orders_rate.postal_code = m2_default_DOT_avg_repair_price.postal_code AND m0_default_DOT_discounted_orders_rate.state = m2_default_DOT_avg_repair_price.state AND m0_default_DOT_discounted_orders_rate.local_region = m2_default_DOT_avg_repair_price.local_region + FULL OUTER JOIN m3_default_DOT_total_repair_cost ON m0_default_DOT_discounted_orders_rate.company_name = m3_default_DOT_total_repair_cost.company_name AND m0_default_DOT_discounted_orders_rate.city = m3_default_DOT_total_repair_cost.city AND m0_default_DOT_discounted_orders_rate.country = m3_default_DOT_total_repair_cost.country AND m0_default_DOT_discounted_orders_rate.postal_code = m3_default_DOT_total_repair_cost.postal_code AND m0_default_DOT_discounted_orders_rate.state = m3_default_DOT_total_repair_cost.state AND m0_default_DOT_discounted_orders_rate.local_region = m3_default_DOT_total_repair_cost.local_region + FULL OUTER JOIN m4_default_DOT_total_repair_order_discounts ON m0_default_DOT_discounted_orders_rate.company_name = m4_default_DOT_total_repair_order_discounts.company_name AND m0_default_DOT_discounted_orders_rate.city = m4_default_DOT_total_repair_order_discounts.city AND m0_default_DOT_discounted_orders_rate.country = m4_default_DOT_total_repair_order_discounts.country AND m0_default_DOT_discounted_orders_rate.postal_code = m4_default_DOT_total_repair_order_discounts.postal_code AND m0_default_DOT_discounted_orders_rate.state = m4_default_DOT_total_repair_order_discounts.state AND m0_default_DOT_discounted_orders_rate.local_region = m4_default_DOT_total_repair_order_discounts.local_region + FULL OUTER JOIN m5_default_DOT_double_total_repair_cost ON m0_default_DOT_discounted_orders_rate.company_name = m5_default_DOT_double_total_repair_cost.company_name AND m0_default_DOT_discounted_orders_rate.city = m5_default_DOT_double_total_repair_cost.city AND m0_default_DOT_discounted_orders_rate.country = m5_default_DOT_double_total_repair_cost.country AND m0_default_DOT_discounted_orders_rate.postal_code = m5_default_DOT_double_total_repair_cost.postal_code AND m0_default_DOT_discounted_orders_rate.state = m5_default_DOT_double_total_repair_cost.state AND m0_default_DOT_discounted_orders_rate.local_region = m5_default_DOT_double_total_repair_cost.local_region + """ + assert compare_query_strings( + data["materializations"][0]["config"]["query"], + expected_materialization_query, + ) + assert data["materializations"][0]["job"] == "DefaultCubeMaterialization" + assert ( + data["materializations"][0]["config"]["measures"] == repair_orders_cube_measures + ) + + +def test_add_materialization_cube_failures( + client_with_repairs_cube: TestClient, # pylint: disable=redefined-outer-name +): + """ + Verifies failure modes when adding materialization config to cube nodes + """ + response = client_with_repairs_cube.post( + "/nodes/default.repairs_cube/materialization/", + json={ + "engine": {"name": "druid", "version": ""}, + "config": {}, + "schedule": "", + }, + ) + assert response.json()["message"] == ( + "No change has been made to the materialization config for node " + "`default.repairs_cube` and engine `druid` as the config does not have valid " + "configuration for engine `druid`." + ) + + response = client_with_repairs_cube.post( + "/nodes/default.repairs_cube/materialization/", + json={ + "engine": {"name": "druid", "version": ""}, + "config": { + "druid": { + "granularity": "DAY", + "timestamp_column": "something", + }, + "partitions": [ + { + "name": "something", + "type_": "categorical", + "values": ["1"], + }, + ], + "spark": {}, + }, + "schedule": "", + }, + ) + assert ( + response.json()["message"] + == "Druid ingestion requires a temporal partition to be specified" + ) + + response = client_with_repairs_cube.post( + "/nodes/default.repairs_cube/materialization/", + json={ + "engine": {"name": "druid", "version": ""}, + "config": { + "druid": {"a": "b"}, + "spark": {}, + }, + "schedule": "", + }, + ) + assert response.json()["message"] == ( + "No change has been made to the materialization config for node " + "`default.repairs_cube` and engine `druid` as the config does not have " + "valid configuration for engine `druid`." + ) + + +def test_add_materialization_config_to_cube( + client_with_repairs_cube: TestClient, # pylint: disable=redefined-outer-name + query_service_client: Iterator[QueryServiceClient], +): + """ + Verifies adding materialization config to a cube + """ + response = client_with_repairs_cube.post( + "/nodes/default.repairs_cube/materialization/", + json={ + "engine": {"name": "druid", "version": ""}, + "config": { + "druid": { + "granularity": "DAY", + "timestamp_column": "date_int", + "intervals": ["2021-01-01/2022-01-01"], + }, + "spark": {}, + "partitions": [ + { + "name": "date_int", + "type_": "temporal", + "values": [], + "range": [20210101, 20220101], + }, + ], + }, + "schedule": "", + }, + ) + assert response.json() == { + "message": "Successfully updated materialization config named `date_int_0` " + "for node `default.repairs_cube`", + "urls": [["http://fake.url/job"]], + } + called_kwargs = [ + call_[0] + for call_ in query_service_client.materialize.call_args_list # type: ignore + ][0][0] + assert called_kwargs.name == "date_int_0" + assert called_kwargs.node_name == "default.repairs_cube" + assert called_kwargs.node_type == "cube" + assert called_kwargs.schedule == "@daily" + assert called_kwargs.spark_conf == {} + dimensions_sorted = sorted( + called_kwargs.druid_spec["dataSchema"]["parser"]["parseSpec"]["dimensionsSpec"][ + "dimensions" + ], + ) + called_kwargs.druid_spec["dataSchema"]["parser"]["parseSpec"]["dimensionsSpec"][ + "dimensions" + ] = dimensions_sorted + called_kwargs.druid_spec["dataSchema"]["metricsSpec"] = sorted( + called_kwargs.druid_spec["dataSchema"]["metricsSpec"], + key=lambda x: x["fieldName"], + ) + assert called_kwargs.druid_spec == { + "dataSchema": { + "dataSource": "default_DOT_repairs_cube", + "granularitySpec": { + "intervals": ["2021-01-01/2022-01-01"], + "segmentGranularity": "DAY", + "type": "uniform", + }, + "metricsSpec": [ + { + "fieldName": "m0_default_DOT_discounted_orders_rate_discount_sum", + "name": "discount_sum", + "type": "longSum", + }, + { + "fieldName": "m0_default_DOT_discounted_orders_rate_placeholder_count", + "name": "placeholder_count", + "type": "longSum", + }, + { + "fieldName": "m1_default_DOT_num_repair_orders_repair_order_id_count", + "name": "repair_order_id_count", + "type": "longSum", + }, + { + "fieldName": "m2_default_DOT_avg_repair_price_price_count", + "name": "price_count", + "type": "longSum", + }, + { + "fieldName": "m4_default_DOT_total_repair_order_discounts_price_discount_sum", + "name": "price_discount_sum", + "type": "doubleSum", + }, + { + "fieldName": "m5_default_DOT_double_total_repair_cost_price_sum", + "name": "price_sum", + "type": "doubleSum", + }, + ], + "parser": { + "parseSpec": { + "dimensionsSpec": { + "dimensions": [ + "city", + "company_name", + "country", + "local_region", + "postal_code", + "state", + ], + }, + "format": "parquet", + "timestampSpec": {"column": "date_int", "format": "yyyyMMdd"}, + }, + }, + }, + } + response = client_with_repairs_cube.get("/nodes/default.repairs_cube/") + materializations = response.json()["materializations"] + assert len(materializations) == 2 + druid_materialization = [ + materialization + for materialization in materializations + if materialization["engine"]["name"] == "druid" + ][0] + assert druid_materialization["engine"] == { + "name": "druid", + "version": "", + "uri": None, + "dialect": "druid", + } + assert set(druid_materialization["config"]["dimensions"]) == { + "postal_code", + "city", + "local_region", + "country", + "state", + "company_name", + } + assert druid_materialization["config"]["partitions"] == [ + { + "name": "date_int", + "values": [], + "range": [20210101, 20220101], + "expression": None, + "type_": "temporal", + }, + ] + assert druid_materialization["schedule"] == "@daily" + + +def test_add_availability_to_cube( + client_with_repairs_cube: TestClient, # pylint: disable=redefined-outer-name +): + """ + Test generating SQL for metrics + dimensions in a cube after adding a cube materialization + """ + client_with_repairs_cube.post( + "/data/default.repairs_cube/availability/", + json={ + "catalog": "default", + "schema_": "roads", + "table": "repairs_cube", + "valid_through_ts": 1010129120, + }, + ) + response = client_with_repairs_cube.get( + "/sql/", + params={ + "metrics": [ + "default.discounted_orders_rate", + "default.num_repair_orders", + "default.avg_repair_price", + ], + "dimensions": [ + "default.hard_hat.country", + "default.hard_hat.postal_code", + ], + "filters": [], + "orderby": [], + "limit": 100, + }, + ) + assert response.json() == { + "columns": [ + {"name": "default_DOT_discounted_orders_rate", "type": "double"}, + {"name": "default_DOT_num_repair_orders", "type": "bigint"}, + {"name": "default_DOT_avg_repair_price", "type": "double"}, + {"name": "country", "type": "string"}, + {"name": "postal_code", "type": "string"}, + ], + "dialect": "spark", + "sql": "SELECT sum(discount_sum) / count(placeholder_count) " + "default_DOT_discounted_orders_rate,\n" + "\tcount(repair_order_id_count) default_DOT_num_repair_orders,\n" + "\tsum(price_sum) / count(price_count) default_DOT_avg_repair_price,\n" + "\tcountry,\n" + "\tpostal_code \n" + " FROM repairs_cube \n" + " GROUP BY country, postal_code\n", + } + + +def test_unlink_node_column_dimension( + client_with_repairs_cube: TestClient, # pylint: disable=redefined-outer-name +): + """ + When a node column link to a dimension is removed, the cube should be invalidated + """ + response = client_with_repairs_cube.delete( + "/nodes/default.repair_order/columns/hard_hat_id/" + "?dimension=default.hard_hat&dimension_column=hard_hat_id", + ) + assert response.json() == { + "message": "The dimension link on the node default.repair_order's hard_hat_id " + "to default.hard_hat has been successfully removed.", + } + response = client_with_repairs_cube.get("/nodes/default.repairs_cube") + data = response.json() + assert data["status"] == "invalid" + + +def test_deactivating_node_upstream_from_cube( + client_with_repairs_cube: TestClient, # pylint: disable=redefined-outer-name +): + """ + Verify deactivating and activating a node upstream from a cube + """ + client_with_repairs_cube.delete("/nodes/default.repair_orders/") + response = client_with_repairs_cube.get("/nodes/default.repairs_cube/") + data = response.json() + assert data["status"] == "invalid" + + client_with_repairs_cube.post("/nodes/default.repair_orders/restore/") + response = client_with_repairs_cube.get("/nodes/default.repairs_cube/") + data = response.json() + assert data["status"] == "valid" + + response = client_with_repairs_cube.get("/cubes/default.repairs_cube/") + data = response.json() + assert data["cube_elements"] == [ + { + "name": "default_DOT_discounted_orders_rate", + "node_name": "default.discounted_orders_rate", + "type": "metric", + }, + { + "name": "default_DOT_num_repair_orders", + "node_name": "default.num_repair_orders", + "type": "metric", + }, + { + "name": "default_DOT_avg_repair_price", + "node_name": "default.avg_repair_price", + "type": "metric", + }, + { + "name": "default_DOT_total_repair_cost", + "node_name": "default.total_repair_cost", + "type": "metric", + }, + { + "name": "default_DOT_total_repair_order_discounts", + "node_name": "default.total_repair_order_discounts", + "type": "metric", + }, + { + "name": "default_DOT_double_total_repair_cost", + "node_name": "default.double_total_repair_cost", + "type": "metric", + }, + {"name": "country", "node_name": "default.hard_hat", "type": "dimension"}, + {"name": "postal_code", "node_name": "default.hard_hat", "type": "dimension"}, + {"name": "city", "node_name": "default.hard_hat", "type": "dimension"}, + {"name": "state", "node_name": "default.hard_hat", "type": "dimension"}, + { + "name": "company_name", + "node_name": "default.dispatcher", + "type": "dimension", + }, + { + "name": "local_region", + "node_name": "default.municipality_dim", + "type": "dimension", + }, + ] diff --git a/datajunction-server/tests/api/data_test.py b/datajunction-server/tests/api/data_test.py new file mode 100644 index 000000000..a42eeb909 --- /dev/null +++ b/datajunction-server/tests/api/data_test.py @@ -0,0 +1,1324 @@ +# pylint: disable=too-many-lines +""" +Tests for the data API. +""" +# pylint: disable=too-many-lines,C0302 +from typing import Dict, List, Optional +from unittest import mock + +import pytest +from fastapi.testclient import TestClient +from sqlmodel import Session, select + +from datajunction_server.models.node import Node + + +class TestDataForNode: + """ + Test ``POST /data/{node_name}/``. + """ + + def test_get_dimension_data_failed( + self, + client_with_examples: TestClient, + ) -> None: + """ + Test trying to get dimensions data while setting dimensions + """ + response = client_with_examples.get( + "/data/default.payment_type/", + params={ + "dimensions": ["something"], + "filters": [], + }, + ) + data = response.json() + assert response.status_code == 422 + assert data["message"] == "Cannot set dimensions for node type dimension!" + + def test_get_dimension_data( + self, + client_with_query_service: TestClient, + ) -> None: + """ + Test trying to get dimensions data while setting dimensions + """ + response = client_with_query_service.get( + "/data/default.payment_type/", + ) + data = response.json() + assert response.status_code == 200 + assert data == { + "id": "0cb5478c-fd7d-4159-a414-68c50f4b9914", + "engine_name": None, + "engine_version": None, + "submitted_query": ( + "SELECT payment_type_table.id,\n\t" + "payment_type_table.payment_type_classification," + "\n\tpayment_type_table.payment_type_name \n FROM " + '"accounting"."payment_type_table" ' + "AS payment_type_table" + ), + "executed_query": None, + "scheduled": None, + "started": None, + "finished": None, + "state": "FINISHED", + "progress": 0.0, + "output_table": None, + "results": [ + { + "sql": "", + "columns": [ + {"name": "id", "type": "int"}, + {"name": "payment_type_classification", "type": "string"}, + {"name": "payment_type_name", "type": "string"}, + ], + "rows": [[1, "CARD", "VISA"], [2, "CARD", "MASTERCARD"]], + "row_count": 0, + }, + ], + "next": None, + "previous": None, + "errors": [], + "links": None, + } + + def test_get_source_data( + self, + client_with_query_service: TestClient, + ) -> None: + """ + Test retrieving data for a source node + """ + response = client_with_query_service.get("/data/default.revenue/") + data = response.json() + assert response.status_code == 200 + assert data == { + "id": "8a8bb03a-74c8-448a-8630-e9439bd5a01b", + "engine_name": None, + "engine_version": None, + "submitted_query": 'SELECT * \n FROM "accounting"."revenue"', + "executed_query": None, + "scheduled": None, + "started": None, + "finished": None, + "state": "FINISHED", + "progress": 0.0, + "output_table": None, + "results": [ + { + "sql": "", + "columns": [{"name": "*", "type": "wildcard"}], + "rows": [[129.19]], + "row_count": 0, + }, + ], + "next": None, + "previous": None, + "errors": [], + "links": None, + } + + def test_get_transform_data( + self, + client_with_query_service: TestClient, + ) -> None: + """ + Test retrieving data for a transform node + """ + response = client_with_query_service.get( + "/data/default.large_revenue_payments_only/", + ) + data = response.json() + assert response.status_code == 200 + assert data == { + "id": "1b049fb1-652e-458a-ba9d-3669412b34bd", + "engine_name": None, + "engine_version": None, + "submitted_query": ( + "SELECT revenue.account_type,\n\trevenue.customer_id,\n\trevenue.payment_amount," + '\n\trevenue.payment_id \n FROM "accounting"."revenue" AS revenue\n \n ' + "WHERE revenue.payment_amount > 1000000" + ), + "executed_query": None, + "scheduled": None, + "started": None, + "finished": None, + "state": "FINISHED", + "progress": 0.0, + "output_table": None, + "results": [ + { + "sql": "", + "columns": [ + {"name": "account_type", "type": "string"}, + {"name": "customer_id", "type": "int"}, + {"name": "payment_amount", "type": "float"}, + {"name": "payment_id", "type": "int"}, + ], + "rows": [ + ["CHECKING", 2, "22.50", 1], + ["SAVINGS", 2, "100.50", 1], + ["CREDIT", 1, "11.50", 1], + ["CHECKING", 2, "2.50", 1], + ], + "row_count": 0, + }, + ], + "next": None, + "previous": None, + "errors": [], + "links": None, + } + + def test_get_metric_data( + self, + client_with_query_service: TestClient, + ) -> None: + """ + Test retrieving data for a metric + """ + response = client_with_query_service.get("/data/basic.num_comments/") + data = response.json() + assert response.status_code == 200 + assert data == { + "id": "ee41ea6c-2303-4fe1-8bf0-f0ce3d6a35ca", + "engine_name": None, + "engine_version": None, + "submitted_query": ( + 'SELECT COUNT(1) basic_DOT_num_comments \n FROM "basic"."comments" ' + "AS basic_DOT_source_DOT_comments" + ), + "executed_query": None, + "scheduled": None, + "started": None, + "finished": None, + "state": "FINISHED", + "progress": 0.0, + "output_table": None, + "results": [ + { + "sql": "", + "columns": [{"name": "basic_DOT_num_comments", "type": "bigint"}], + "rows": [[1]], + "row_count": 0, + }, + ], + "next": None, + "previous": None, + "errors": [], + "links": None, + } + + def test_get_multiple_metrics_and_dimensions_data( + self, + client_with_query_service: TestClient, + ) -> None: + """ + Test getting multiple metrics and dimensions + """ + response = client_with_query_service.get( + "/data?metrics=default.num_repair_orders&metrics=" + "default.avg_repair_price&dimensions=default.dispatcher.company_name&limit=10", + ) + data = response.json() + assert response.status_code == 200 + assert data == { + "id": "bd98d6be-e2d2-413e-94c7-96d9411ddee2", + "engine_name": None, + "engine_version": None, + "submitted_query": ( + "SELECT avg(repair_order_details.price) AS default_DOT_avg_repair_price," + "\\n\\tdispatcher.company_name,\\n\\tcount(repair_orders.repair_order_id) " + "AS default_DOT_num_repair_ordersdefault_DOT_num_repair_orders \\n FROM " + "roads.repair_order_details AS repair_order_details LEFT OUTER JOIN (SELECT " + "repair_orders.dispatcher_id,\\n\\trepair_orders.hard_hat_id,\\n\\t" + "repair_orders.municipality_id,\\n\\trepair_orders.repair_order_id " + "\\n FROM roads.repair_orders AS repair_orders) AS repair_order ON " + "repair_order_details.repair_order_id = repair_order.repair_order_id" + "\\nLEFT OUTER JOIN (SELECT dispatchers.company_name,\\n\\t" + "dispatchers.dispatcher_id \\n FROM roads.dispatchers AS dispatchers) " + "AS dispatcher ON repair_order.dispatcher_id = dispatcher.dispatcher_id " + "\\n GROUP BY dispatcher.company_name\\nLIMIT 10" + ), + "executed_query": None, + "scheduled": None, + "started": None, + "finished": None, + "state": "FINISHED", + "progress": 0.0, + "output_table": None, + "results": [ + { + "sql": "", + "columns": [ + { + "name": "default_DOT_num_repair_orders", + "type": "bigint", + }, + {"name": "default_DOT_avg_repair_price", "type": "double"}, + {"name": "company_name", "type": "string"}, + ], + "rows": [[1.0, "Foo", 100], [2.0, "Bar", 200]], + "row_count": 0, + }, + ], + "next": None, + "previous": None, + "errors": [], + "links": None, + } + + def test_stream_multiple_metrics_and_dimensions_data( + self, + client_with_query_service: TestClient, + ) -> None: + """ + Test streaming query status for multiple metrics and dimensions + """ + response = client_with_query_service.get( + "/stream?metrics=default.num_repair_orders&metrics=" + "default.avg_repair_price&dimensions=default.dispatcher.company_name&limit=10", + headers={ + "Accept": "text/event-stream", + }, + stream=True, + ) + assert response.status_code == 200 + + def test_get_data_for_query_id( + self, + client_with_query_service: TestClient, + ) -> None: + """ + Test retrieving data for a query ID + """ + # run some query + response = client_with_query_service.get("/data/basic.num_comments/") + data = response.json() + assert response.status_code == 200 + assert data["id"] == "ee41ea6c-2303-4fe1-8bf0-f0ce3d6a35ca" + + # and try to get the results by the query id only + new_response = client_with_query_service.get(f"/data/query/{data['id']}/") + new_data = response.json() + assert new_response.status_code == 200 + assert new_data["results"] == [ + { + "sql": "", + "columns": [{"name": "basic_DOT_num_comments", "type": "bigint"}], + "rows": [[1]], + "row_count": 0, + }, + ] + + # and repeat for a bogus query id + yet_another_response = client_with_query_service.get("/data/query/foo-bar-baz/") + assert yet_another_response.status_code == 404 + assert "Query foo-bar-baz not found." in yet_another_response.text + + +class TestAvailabilityState: # pylint: disable=too-many-public-methods + """ + Test ``POST /data/{node_name}/availability/``. + """ + + def test_setting_availability_state( + self, + session: Session, + client_with_examples: TestClient, + ) -> None: + """ + Test adding an availability state + """ + response = client_with_examples.post( + "/data/default.large_revenue_payments_and_business_only/availability/", + json={ + "catalog": "default", + "schema_": "accounting", + "table": "pmts", + "valid_through_ts": 20230125, + "max_temporal_partition": ["2023", "01", "25"], + "min_temporal_partition": ["2022", "01", "01"], + }, + ) + data = response.json() + + assert response.status_code == 200 + assert data == {"message": "Availability state successfully posted"} + + # Check that the history tracker has been updated + response = client_with_examples.get( + "/history?node=default.large_revenue_payments_and_business_only", + ) + data = response.json() + availability_activities = [ + activity for activity in data if activity["entity_type"] == "availability" + ] + assert availability_activities == [ + { + "activity_type": "create", + "created_at": mock.ANY, + "details": {}, + "entity_name": None, + "node": "default.large_revenue_payments_and_business_only", + "entity_type": "availability", + "id": mock.ANY, + "post": { + "catalog": "default", + "categorical_partitions": [], + "max_temporal_partition": ["2023", "01", "25"], + "min_temporal_partition": ["2022", "01", "01"], + "partitions": [], + "schema_": "accounting", + "table": "pmts", + "temporal_partitions": [], + "valid_through_ts": 20230125, + }, + "pre": {}, + "user": None, + }, + ] + + statement = select(Node).where( + Node.name == "default.large_revenue_payments_and_business_only", + ) + large_revenue_payments_and_business_only = session.exec(statement).one() + node_dict = large_revenue_payments_and_business_only.current.availability.dict() + node_dict.pop("updated_at") + assert node_dict == { + "valid_through_ts": 20230125, + "catalog": "default", + "min_temporal_partition": ["2022", "01", "01"], + "table": "pmts", + "max_temporal_partition": ["2023", "01", "25"], + "partitions": [], + "schema_": "accounting", + "id": 1, + "categorical_partitions": [], + "temporal_partitions": [], + } + + def test_availability_catalog_mismatch( + self, + client_with_examples: TestClient, + ) -> None: + """ + Test that setting availability works even when the catalogs do not match + """ + response = client_with_examples.post( + "/data/default.large_revenue_payments_and_business_only/availability/", + json={ + "catalog": "public", + "schema_": "accounting", + "table": "pmts", + "valid_through_ts": 20230125, + "max_temporal_partition": ["2023", "01", "25"], + "min_temporal_partition": ["2022", "01", "01"], + }, + ) + data = response.json() + + assert response.status_code == 200 + assert data["message"] == "Availability state successfully posted" + + def test_setting_availability_state_multiple_times( + self, + session: Session, + client_with_examples: TestClient, + ) -> None: + """ + Test adding multiple availability states + """ + response = client_with_examples.post( + "/data/default.large_revenue_payments_and_business_only/availability/", + json={ + "catalog": "default", + "schema_": "accounting", + "table": "pmts", + "valid_through_ts": 20230125, + "max_temporal_partition": ["2023", "01", "25"], + "min_temporal_partition": ["2022", "01", "01"], + }, + ) + data = response.json() + + assert response.status_code == 200 + assert data == {"message": "Availability state successfully posted"} + + response = client_with_examples.post( + "/data/default.large_revenue_payments_and_business_only/availability/", + json={ + "catalog": "default", + "schema_": "accounting", + "table": "pmts", + "valid_through_ts": 20230125, + "max_temporal_partition": ["2023", "01", "25"], + "min_temporal_partition": ["2022", "01", "01"], + }, + ) + data = response.json() + + assert response.status_code == 200 + assert data == {"message": "Availability state successfully posted"} + + response = client_with_examples.post( + "/data/default.large_revenue_payments_and_business_only/availability/", + json={ + "catalog": "default", + "schema_": "new_accounting", + "table": "new_payments_table", + "valid_through_ts": 20230125, + "max_temporal_partition": ["2023", "01", "25"], + "min_temporal_partition": ["2022", "01", "01"], + "categorical_partitions": [], + "temporal_partitions": [], + }, + ) + data = response.json() + + assert response.status_code == 200 + assert data == {"message": "Availability state successfully posted"} + + # Check that the history tracker has been updated + response = client_with_examples.get( + "/history/?node=default.large_revenue_payments_and_business_only", + ) + data = response.json() + availability_activities = [ + activity for activity in data if activity["entity_type"] == "availability" + ] + assert availability_activities == [ + { + "activity_type": "create", + "created_at": mock.ANY, + "details": {}, + "entity_name": None, + "node": "default.large_revenue_payments_and_business_only", + "entity_type": "availability", + "id": mock.ANY, + "post": { + "catalog": "default", + "categorical_partitions": [], + "max_temporal_partition": ["2023", "01", "25"], + "min_temporal_partition": ["2022", "01", "01"], + "partitions": [], + "schema_": "accounting", + "table": "pmts", + "temporal_partitions": [], + "valid_through_ts": 20230125, + }, + "pre": {}, + "user": None, + }, + { + "activity_type": "create", + "created_at": mock.ANY, + "details": {}, + "entity_name": None, + "node": "default.large_revenue_payments_and_business_only", + "entity_type": "availability", + "id": mock.ANY, + "post": { + "catalog": "default", + "categorical_partitions": [], + "max_temporal_partition": ["2023", "01", "25"], + "min_temporal_partition": ["2022", "01", "01"], + "partitions": [], + "schema_": "accounting", + "table": "pmts", + "temporal_partitions": [], + "valid_through_ts": 20230125, + }, + "pre": { + "catalog": "default", + "categorical_partitions": [], + "max_temporal_partition": ["2023", "01", "25"], + "min_temporal_partition": ["2022", "01", "01"], + "partitions": [], + "schema_": "accounting", + "table": "pmts", + "temporal_partitions": [], + "valid_through_ts": 20230125, + }, + "user": None, + }, + { + "activity_type": "create", + "created_at": mock.ANY, + "details": {}, + "entity_name": None, + "node": "default.large_revenue_payments_and_business_only", + "entity_type": "availability", + "id": mock.ANY, + "post": { + "catalog": "default", + "categorical_partitions": [], + "max_temporal_partition": ["2023", "01", "25"], + "min_temporal_partition": ["2022", "01", "01"], + "partitions": [], + "schema_": "new_accounting", + "table": "new_payments_table", + "temporal_partitions": [], + "valid_through_ts": 20230125, + }, + "pre": { + "catalog": "default", + "categorical_partitions": [], + "max_temporal_partition": ["2023", "01", "25"], + "min_temporal_partition": ["2022", "01", "01"], + "partitions": [], + "schema_": "accounting", + "table": "pmts", + "temporal_partitions": [], + "valid_through_ts": 20230125, + }, + "user": None, + }, + ] + + statement = select(Node).where( + Node.name == "default.large_revenue_payments_and_business_only", + ) + large_revenue_payments_and_business_only = session.exec(statement).one() + node_dict = large_revenue_payments_and_business_only.current.availability.dict() + node_dict.pop("updated_at") + assert node_dict == { + "valid_through_ts": 20230125, + "catalog": "default", + "min_temporal_partition": ["2022", "01", "01"], + "table": "new_payments_table", + "max_temporal_partition": ["2023", "01", "25"], + "partitions": [], + "schema_": "new_accounting", + "id": 3, + "categorical_partitions": [], + "temporal_partitions": [], + } + + def test_that_update_at_timestamp_is_being_updated( + self, + session: Session, + client_with_examples: TestClient, + ) -> None: + """ + Test that the `updated_at` attribute is being updated + """ + response = client_with_examples.post( + "/data/default.large_revenue_payments_and_business_only/availability/", + json={ + "catalog": "default", + "schema_": "accounting", + "table": "pmts", + "valid_through_ts": 20230125, + "max_temporal_partition": ["2023", "01", "25"], + "min_temporal_partition": ["2022", "01", "01"], + }, + ) + assert response.status_code == 200 + statement = select(Node).where( + Node.name == "default.large_revenue_payments_and_business_only", + ) + large_revenue_payments_and_business_only = session.exec(statement).one() + updated_at_1 = ( + large_revenue_payments_and_business_only.current.availability.dict()[ + "updated_at" + ] + ) + + response = client_with_examples.post( + "/data/default.large_revenue_payments_and_business_only/availability/", + json={ + "catalog": "default", + "schema_": "accounting", + "table": "pmts", + "valid_through_ts": 20230125, + "max_temporal_partition": ["2023", "01", "25"], + "min_temporal_partition": ["2022", "01", "01"], + }, + ) + assert response.status_code == 200 + + session.refresh(large_revenue_payments_and_business_only) + updated_at_2 = ( + large_revenue_payments_and_business_only.current.availability.dict()[ + "updated_at" + ] + ) + + assert updated_at_2 > updated_at_1 + + def test_raising_when_node_does_not_exist( + self, + client_with_examples: TestClient, + ) -> None: + """ + Test raising when setting availability state on non-existent node + """ + response = client_with_examples.post( + "/data/default.nonexistentnode/availability/", + json={ + "catalog": "default", + "schema_": "accounting", + "table": "pmts", + "valid_through_ts": 20230125, + "max_temporal_partition": ["2023", "01", "25"], + "min_temporal_partition": ["2022", "01", "01"], + }, + ) + data = response.json() + + assert response.status_code == 404 + assert data == { + "message": "A node with name `default.nonexistentnode` does not exist.", + "errors": [], + "warnings": [], + } + + def test_merging_in_a_higher_max_partition( + self, + session: Session, + client_with_examples: TestClient, + ) -> None: + """ + Test that the higher max_partition value is used when merging in an availability state + """ + client_with_examples.post( + "/data/default.large_revenue_payments_only/availability/", + json={ + "catalog": "default", + "schema_": "accounting", + "table": "large_pmts", + "valid_through_ts": 20230101, + "max_temporal_partition": ["2023", "01", "01"], + "min_temporal_partition": ["2022", "01", "01"], + }, + ) + response = client_with_examples.post( + "/data/default.large_revenue_payments_only/availability/", + json={ + "catalog": "default", + "schema_": "accounting", + "table": "large_pmts", + "valid_through_ts": 20230102, + "max_temporal_partition": [ + "2023", + "01", + "02", + ], # should be used since it's a higher max_temporal_partition + "min_temporal_partition": [ + "2023", + "01", + "02", + ], # should be ignored since it's a higher min_temporal_partition + }, + ) + data = response.json() + + assert response.status_code == 200 + assert data == {"message": "Availability state successfully posted"} + + statement = select(Node).where( + Node.name == "default.large_revenue_payments_only", + ) + large_revenue_payments_only = session.exec(statement).one() + node_dict = large_revenue_payments_only.current.availability.dict() + node_dict.pop("updated_at") + assert node_dict == { + "valid_through_ts": 20230102, + "catalog": "default", + "min_temporal_partition": ["2022", "01", "01"], + "table": "large_pmts", + "max_temporal_partition": ["2023", "01", "02"], + "schema_": "accounting", + "partitions": [], + "id": 2, + "categorical_partitions": [], + "temporal_partitions": [], + } + + @pytest.fixture + def post_local_hard_hats_availability(self, client_with_examples: TestClient): + """ + Fixture for posting availability for local_hard_hats + """ + + def _post( + min_temporal_partition: Optional[List[str]] = None, + max_temporal_partition: Optional[List[str]] = None, + partitions: List[Dict] = None, + categorical_partitions: List[str] = None, + ): + if categorical_partitions is None: + categorical_partitions = ["country", "postal_code"] + return client_with_examples.post( + "/data/default.local_hard_hats/availability/", + json={ + "catalog": "default", + "schema_": "dimensions", + "table": "local_hard_hats", + "valid_through_ts": 20230101, + "categorical_partitions": categorical_partitions, + "temporal_partitions": ["birth_date"], + "min_temporal_partition": min_temporal_partition, + "max_temporal_partition": max_temporal_partition, + "partitions": partitions, + }, + ) + + return _post + + def test_set_temporal_only_availability( + self, + client_with_examples: TestClient, + post_local_hard_hats_availability, + ): + """ + Test setting availability on a node where it only has temporal partitions and + no categorical partitions. + """ + post_local_hard_hats_availability( + min_temporal_partition=["20230101"], + max_temporal_partition=["20230105"], + partitions=[], + categorical_partitions=[], + ) + post_local_hard_hats_availability( + min_temporal_partition=["20230101"], + max_temporal_partition=["20230110"], + partitions=[], + categorical_partitions=[], + ) + + response = client_with_examples.get( + "/nodes/default.local_hard_hats/", + ) + assert response.json()["availability"] == { + "catalog": "default", + "id": mock.ANY, + "min_temporal_partition": ["20230101"], + "max_temporal_partition": ["20230110"], + "categorical_partitions": [], + "temporal_partitions": ["birth_date"], + "partitions": [], + "schema_": "dimensions", + "table": "local_hard_hats", + "updated_at": mock.ANY, + "valid_through_ts": 20230101, + } + + def test_set_node_level_availability_wider_time_range( + self, + client_with_examples: TestClient, + post_local_hard_hats_availability, + ): + """ + The node starts off with partition-level availability with a specific time range. + We add in a node-level availability with a wider time range than at the partition + level. We expect this new availability state to overwrite the partition-level + availability. + """ + # Set initial availability state + post_local_hard_hats_availability( + partitions=[ + { + "value": ["DE", "ABC123D"], + "min_temporal_partition": ["20230101"], + "max_temporal_partition": ["20230105"], + "valid_through_ts": 20230101, + }, + ], + ) + # Post wider availability + post_local_hard_hats_availability( + min_temporal_partition=["20230101"], + max_temporal_partition=["20230110"], + partitions=[], + ) + + response = client_with_examples.get( + "/nodes/default.local_hard_hats/", + ) + assert response.json()["availability"] == { + "catalog": "default", + "id": mock.ANY, + "min_temporal_partition": ["20230101"], + "max_temporal_partition": ["20230110"], + "categorical_partitions": ["country", "postal_code"], + "temporal_partitions": ["birth_date"], + "partitions": [], + "schema_": "dimensions", + "table": "local_hard_hats", + "updated_at": mock.ANY, + "valid_through_ts": 20230101, + } + + def test_set_node_level_availability_smaller_time_range( + self, + client_with_examples: TestClient, + post_local_hard_hats_availability, + ): + """ + Set a node level availability with a smaller time range than the existing + one will result in no change to the merged availability state + """ + post_local_hard_hats_availability( + min_temporal_partition=["20230101"], + max_temporal_partition=["20230110"], + partitions=[], + ) + + post_local_hard_hats_availability( + min_temporal_partition=["20230103"], + max_temporal_partition=["20230105"], + partitions=[], + ) + + response = client_with_examples.get( + "/nodes/default.local_hard_hats/", + ) + availability = response.json()["availability"] + assert availability["min_temporal_partition"] == ["20230101"] + assert availability["max_temporal_partition"] == ["20230110"] + assert availability["partitions"] == [] + + def test_set_partition_level_availability_smaller_time_range( + self, + client_with_examples: TestClient, + post_local_hard_hats_availability, + ): + """ + Set a partition-level availability with a smaller time range than + the existing node-level time range will result in no change to the + merged availability state + """ + post_local_hard_hats_availability( + min_temporal_partition=["20230101"], + max_temporal_partition=["20230110"], + partitions=[], + ) + + post_local_hard_hats_availability( + partitions=[ + { + "value": ["DE", None], + "min_temporal_partition": ["20230102"], + "max_temporal_partition": ["20230107"], + "valid_through_ts": 20230101, + }, + ], + ) + + response = client_with_examples.get( + "/nodes/default.local_hard_hats/", + ) + availability = response.json()["availability"] + assert availability["min_temporal_partition"] == ["20230101"] + assert availability["max_temporal_partition"] == ["20230110"] + assert availability["partitions"] == [] + + def test_set_partition_level_availability_larger_time_range( + self, + client_with_examples: TestClient, + post_local_hard_hats_availability, + ): + """ + Set a partition-level availability with a larger time range than + the existing node-level time range will result in the partition with + the larger range being recorded + """ + post_local_hard_hats_availability( + min_temporal_partition=["20230101"], + max_temporal_partition=["20230110"], + partitions=[], + ) + + post_local_hard_hats_availability( + partitions=[ + { + "value": ["DE", None], + "min_temporal_partition": ["20230102"], + "max_temporal_partition": ["20230115"], + "valid_through_ts": 20230101, + }, + ], + ) + + response = client_with_examples.get( + "/nodes/default.local_hard_hats/", + ) + availability = response.json()["availability"] + assert availability["min_temporal_partition"] == ["20230101"] + assert availability["max_temporal_partition"] == ["20230110"] + assert availability["partitions"] == [ + { + "value": ["DE", None], + "min_temporal_partition": ["20230102"], + "max_temporal_partition": ["20230115"], + "valid_through_ts": 20230101, + }, + ] + + def test_set_orthogonal_partition_level_availability( + self, + client_with_examples: TestClient, + post_local_hard_hats_availability, + ): + """ + Test setting an orthogonal partition-level availability. + """ + post_local_hard_hats_availability( + min_temporal_partition=["20230101"], + max_temporal_partition=["20230110"], + partitions=[ + { + "value": ["DE", None], + "min_temporal_partition": ["20230102"], + "max_temporal_partition": ["20230115"], + "valid_through_ts": 20230101, + }, + ], + ) + + post_local_hard_hats_availability( + partitions=[ + { + "value": ["MY", None], + "min_temporal_partition": ["20230102"], + "max_temporal_partition": ["20230115"], + "valid_through_ts": 20230101, + }, + ], + ) + + response = client_with_examples.get( + "/nodes/default.local_hard_hats/", + ) + availability = response.json()["availability"] + assert availability["min_temporal_partition"] == ["20230101"] + assert availability["max_temporal_partition"] == ["20230110"] + assert availability["partitions"] == [ + { + "value": ["DE", None], + "min_temporal_partition": ["20230102"], + "max_temporal_partition": ["20230115"], + "valid_through_ts": 20230101, + }, + { + "value": ["MY", None], + "min_temporal_partition": ["20230102"], + "max_temporal_partition": ["20230115"], + "valid_through_ts": 20230101, + }, + ] + + def test_set_overlap_partition_level_availability( + self, + client_with_examples: TestClient, + post_local_hard_hats_availability, + ): + """ + Test setting an overlapping partition-level availability. + """ + post_local_hard_hats_availability( + min_temporal_partition=["20230101"], + max_temporal_partition=["20230110"], + partitions=[ + { + "value": ["DE", None], + "min_temporal_partition": ["20230102"], + "max_temporal_partition": ["20230115"], + "valid_through_ts": 20230101, + }, + ], + ) + + post_local_hard_hats_availability( + partitions=[ + { + "value": ["DE", None], + "min_temporal_partition": ["20230105"], + "max_temporal_partition": ["20230215"], + "valid_through_ts": 20230101, + }, + ], + ) + + response = client_with_examples.get( + "/nodes/default.local_hard_hats/", + ) + availability = response.json()["availability"] + assert availability["min_temporal_partition"] == ["20230101"] + assert availability["max_temporal_partition"] == ["20230110"] + assert availability["partitions"] == [ + { + "value": ["DE", None], + "min_temporal_partition": ["20230102"], + "max_temporal_partition": ["20230215"], + "valid_through_ts": 20230101, + }, + ] + + def test_set_semioverlap_partition_level_availability( + self, + client_with_examples: TestClient, + post_local_hard_hats_availability, + ): + """ + Test setting a semi-overlapping partition-level availability. + """ + post_local_hard_hats_availability( + min_temporal_partition=["20230101"], + max_temporal_partition=["20230110"], + partitions=[ + { + "value": ["DE", None], + "min_temporal_partition": ["20230102"], + "max_temporal_partition": ["20230115"], + "valid_through_ts": 20230101, + }, + { + "value": ["DE", "abc-def"], + "min_temporal_partition": ["20230202"], + "max_temporal_partition": ["20230215"], + "valid_through_ts": 20230101, + }, + ], + ) + + post_local_hard_hats_availability( + partitions=[ + { + "value": ["DE", None], + "min_temporal_partition": ["20230102"], + "max_temporal_partition": ["20230115"], + "valid_through_ts": 20230101, + }, + { + "value": ["DE", "abc-def"], + "min_temporal_partition": ["20230102"], + "max_temporal_partition": ["20230215"], + "valid_through_ts": 20230101, + }, + ], + ) + + response = client_with_examples.get( + "/nodes/default.local_hard_hats/", + ) + availability = response.json()["availability"] + assert availability["min_temporal_partition"] == ["20230101"] + assert availability["max_temporal_partition"] == ["20230110"] + assert availability["partitions"] == [ + { + "value": ["DE", "abc-def"], + "min_temporal_partition": ["20230102"], + "max_temporal_partition": ["20230215"], + "valid_through_ts": 20230101, + }, + { + "value": ["DE", None], + "min_temporal_partition": ["20230102"], + "max_temporal_partition": ["20230115"], + "valid_through_ts": 20230101, + }, + ] + + def test_merging_in_a_lower_min_partition( + self, + session: Session, + client_with_examples: TestClient, + ) -> None: + """ + Test that the lower min_partition value is used when merging in an availability state + """ + client_with_examples.post( + "/data/default.large_revenue_payments_only/availability/", + json={ + "catalog": "default", + "schema_": "accounting", + "table": "large_pmts", + "valid_through_ts": 20230101, + "max_temporal_partition": ["2023", "01", "01"], + "min_temporal_partition": ["2022", "01", "01"], + }, + ) + response = client_with_examples.post( + "/data/default.large_revenue_payments_only/availability/", + json={ + "catalog": "default", + "schema_": "accounting", + "table": "large_pmts", + "valid_through_ts": 20230101, + "max_temporal_partition": [ + "2021", + "12", + "31", + ], # should be ignored since it's a lower max_temporal_partition + "min_temporal_partition": [ + "2021", + "12", + "31", + ], # should be used since it's a lower min_partition + }, + ) + data = response.json() + + assert response.status_code == 200 + assert data == {"message": "Availability state successfully posted"} + + statement = select(Node).where( + Node.name == "default.large_revenue_payments_only", + ) + large_revenue_payments_only = session.exec(statement).one() + node_dict = large_revenue_payments_only.current.availability.dict() + node_dict.pop("updated_at") + assert node_dict == { + "valid_through_ts": 20230101, + "catalog": "default", + "min_temporal_partition": ["2021", "12", "31"], + "categorical_partitions": [], + "temporal_partitions": [], + "table": "large_pmts", + "max_temporal_partition": ["2023", "01", "01"], + "schema_": "accounting", + "partitions": [], + "id": 2, + } + + def test_moving_back_valid_through_ts( + self, + session: Session, + client_with_examples: TestClient, + ) -> None: + """ + Test that the valid through timestamp can be moved backwards + """ + client_with_examples.post( + "/data/default.large_revenue_payments_only/availability/", + json={ + "catalog": "default", + "schema_": "accounting", + "table": "large_pmts", + "valid_through_ts": 20230101, + "max_temporal_partition": ["2023", "01", "01"], + "min_temporal_partition": ["2022", "01", "01"], + }, + ) + response = client_with_examples.post( + "/data/default.large_revenue_payments_only/availability/", + json={ + "catalog": "default", + "schema_": "accounting", + "table": "large_pmts", + "valid_through_ts": 20221231, + "max_temporal_partition": [ + "2023", + "01", + "01", + ], # should be ignored since it's a lower max_temporal_partition + "min_temporal_partition": [ + "2022", + "01", + "01", + ], # should be used since it's a lower min_temporal_partition + }, + ) + data = response.json() + + assert response.status_code == 200 + assert data == {"message": "Availability state successfully posted"} + + statement = select(Node).where( + Node.name == "default.large_revenue_payments_only", + ) + large_revenue_payments_only = session.exec(statement).one() + node_dict = large_revenue_payments_only.current.availability.dict() + node_dict.pop("updated_at") + assert node_dict == { + "valid_through_ts": 20221231, + "catalog": "default", + "min_temporal_partition": ["2022", "01", "01"], + "table": "large_pmts", + "max_temporal_partition": ["2023", "01", "01"], + "schema_": "accounting", + "partitions": [], + "id": 2, + "categorical_partitions": [], + "temporal_partitions": [], + } + + def test_setting_availablity_state_on_a_source_node( + self, + session: Session, + client_with_examples: TestClient, + ) -> None: + """ + Test setting the availability state on a source node + """ + response = client_with_examples.post( + "/data/default.revenue/availability/", + json={ + "catalog": "default", + "schema_": "accounting", + "table": "revenue", + "valid_through_ts": 20230101, + "max_temporal_partition": ["2023", "01", "01"], + "min_temporal_partition": ["2022", "01", "01"], + }, + ) + data = response.json() + + assert response.status_code == 200 + assert data == {"message": "Availability state successfully posted"} + + statement = select(Node).where( + Node.name == "default.revenue", + ) + revenue = session.exec(statement).one() + node_dict = revenue.current.availability.dict() + node_dict.pop("updated_at") + assert node_dict == { + "valid_through_ts": 20230101, + "catalog": "default", + "min_temporal_partition": ["2022", "01", "01"], + "table": "revenue", + "max_temporal_partition": ["2023", "01", "01"], + "schema_": "accounting", + "partitions": [], + "id": 1, + "categorical_partitions": [], + "temporal_partitions": [], + } + + def test_raise_on_setting_invalid_availability_state_on_a_source_node( + self, + client_with_examples: TestClient, + ) -> None: + """ + Test raising availability state doesn't match existing source node table + """ + response = client_with_examples.post( + "/data/default.revenue/availability/", + json={ + "catalog": "default", + "schema_": "accounting", + "table": "large_pmts", + "valid_through_ts": 20230101, + "max_temporal_partition": ["2023", "01", "01"], + "min_temporal_partition": ["2022", "01", "01"], + }, + ) + data = response.json() + + assert response.status_code == 500 + assert data == { + "message": ( + "Cannot set availability state, source nodes require availability states " + "to match the set table: default.accounting.large_pmts does not match " + "default.accounting.revenue " + ), + "errors": [], + "warnings": [], + } diff --git a/datajunction-server/tests/api/dimensions_test.py b/datajunction-server/tests/api/dimensions_test.py new file mode 100644 index 000000000..008236aff --- /dev/null +++ b/datajunction-server/tests/api/dimensions_test.py @@ -0,0 +1,128 @@ +""" +Tests for the dimensions API. +""" +from fastapi.testclient import TestClient + + +def test_list_dimension(client_with_examples: TestClient) -> None: + """ + Test ``GET /dimensions/``. + """ + response = client_with_examples.get("/dimensions/") + data = response.json() + + assert response.status_code == 200 + assert len(data) > 10 + + +def test_list_nodes_with_dimension(client_with_examples: TestClient) -> None: + """ + Test ``GET /dimensions/{name}/nodes/``. + """ + response = client_with_examples.get("/dimensions/default.hard_hat/nodes/") + data = response.json() + roads_nodes = [ + "default.repair_orders", + "default.repair_order_details", + "default.regional_level_agg", + "default.national_level_agg", + "default.regional_repair_efficiency", + "default.num_repair_orders", + "default.avg_repair_price", + "default.total_repair_cost", + "default.discounted_orders_rate", + "default.total_repair_order_discounts", + "default.avg_repair_order_discounts", + "default.avg_time_to_dispatch", + ] + assert [node["name"] for node in data] == roads_nodes + + response = client_with_examples.get("/dimensions/default.repair_order/nodes/") + data = response.json() + assert [node["name"] for node in data] == roads_nodes + + response = client_with_examples.get("/dimensions/default.us_state/nodes/") + data = response.json() + assert [node["name"] for node in data] == roads_nodes + + response = client_with_examples.get("/dimensions/default.municipality_dim/nodes/") + data = response.json() + assert [node["name"] for node in data] == roads_nodes + + response = client_with_examples.get("/dimensions/default.contractor/nodes/") + data = response.json() + assert [node["name"] for node in data] == [ + "default.repair_type", + "default.regional_level_agg", + "default.regional_repair_efficiency", + ] + + response = client_with_examples.get( + "/dimensions/default.municipality_dim/nodes/?node_type=metric", + ) + data = response.json() + assert [node["name"] for node in data] == [ + "default.regional_repair_efficiency", + "default.num_repair_orders", + "default.avg_repair_price", + "default.total_repair_cost", + "default.discounted_orders_rate", + "default.total_repair_order_discounts", + "default.avg_repair_order_discounts", + "default.avg_time_to_dispatch", + ] + + +def test_list_nodes_with_common_dimension(client_with_examples: TestClient) -> None: + """ + Test ``GET /dimensions/common/``. + """ + response = client_with_examples.get( + "/dimensions/common/?dimension=default.hard_hat", + ) + data = response.json() + roads_nodes = [ + "default.repair_orders", + "default.repair_order_details", + "default.regional_level_agg", + "default.national_level_agg", + "default.regional_repair_efficiency", + "default.num_repair_orders", + "default.avg_repair_price", + "default.total_repair_cost", + "default.discounted_orders_rate", + "default.total_repair_order_discounts", + "default.avg_repair_order_discounts", + "default.avg_time_to_dispatch", + ] + assert [node["name"] for node in data] == roads_nodes + + response = client_with_examples.get( + "/dimensions/common/?dimension=default.hard_hat&dimension=default.us_state" + "&dimension=default.dispatcher&dimension=default.municipality_dim", + ) + data = response.json() + assert [node["name"] for node in data] == roads_nodes + + response = client_with_examples.get( + "/dimensions/common/?dimension=default.hard_hat&dimension=default.us_state" + "&dimension=default.dispatcher&dimension=default.payment_type", + ) + data = response.json() + assert [node["name"] for node in data] == [] + + response = client_with_examples.get( + "/dimensions/common/?dimension=default.hard_hat&dimension=default.us_state" + "&dimension=default.dispatcher&node_type=metric", + ) + data = response.json() + assert [node["name"] for node in data] == [ + "default.regional_repair_efficiency", + "default.num_repair_orders", + "default.avg_repair_price", + "default.total_repair_cost", + "default.discounted_orders_rate", + "default.total_repair_order_discounts", + "default.avg_repair_order_discounts", + "default.avg_time_to_dispatch", + ] diff --git a/datajunction-server/tests/api/djql_test.py b/datajunction-server/tests/api/djql_test.py new file mode 100644 index 000000000..3e887345e --- /dev/null +++ b/datajunction-server/tests/api/djql_test.py @@ -0,0 +1,383 @@ +""" +Tests for the djsql API. +""" +# pylint: disable=too-many-lines,C0301 + +from fastapi.testclient import TestClient + +from tests.sql.utils import compare_query_strings + + +def test_get_djsql_data_only_nodes_query( + client_with_query_service: TestClient, +) -> None: + """ + Test djsql with just some non-metric nodes + """ + + query = """ +SELECT default.hard_hat.country, + default.hard_hat.city +FROM default.hard_hat + """ + + response = client_with_query_service.get( + "/djsql/data/", + params={"query": query}, + ) + query = response.json()["results"][0]["sql"] + expected_query = """WITH +node_query_0 AS (SELECT default_DOT_hard_hats.address, + default_DOT_hard_hats.birth_date, + default_DOT_hard_hats.city, + default_DOT_hard_hats.contractor_id, + default_DOT_hard_hats.country, + default_DOT_hard_hats.first_name, + default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.hire_date, + default_DOT_hard_hats.last_name, + default_DOT_hard_hats.manager, + default_DOT_hard_hats.postal_code, + default_DOT_hard_hats.state, + default_DOT_hard_hats.title + FROM roads.hard_hats AS default_DOT_hard_hats + +) + +SELECT node_query_0.country, + node_query_0.city + FROM node_query_0""" + + assert compare_query_strings(query, expected_query) + + +def test_get_djsql_data_only_nested_metrics( + client_with_query_service: TestClient, +) -> None: + """ + Test djsql with metric subquery + """ + + query = """ + SELECT + Sum(avg_repair_price), + city + FROM + ( + SELECT + default.avg_repair_price avg_repair_price, + default.hard_hat.country, + default.hard_hat.city + FROM + metrics + GROUP BY + default.hard_hat.country, + default.hard_hat.city + LIMIT + 5 + ) + GROUP BY city + """ + + response = client_with_query_service.get( + "/djsql/data/", + params={"query": query}, + ) + query = response.json()["results"][0]["sql"] + expected_query = """WITH +metric_query_0 AS (SELECT m0_default_DOT_avg_repair_price.default_DOT_avg_repair_price, + m0_default_DOT_avg_repair_price.city, + m0_default_DOT_avg_repair_price.country + FROM (SELECT default_DOT_hard_hat.city, + default_DOT_hard_hat.country, + avg(default_DOT_repair_order_details.price) default_DOT_avg_repair_price + FROM roads.repair_order_details AS default_DOT_repair_order_details LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders) + AS default_DOT_repair_order ON default_DOT_repair_order_details.repair_order_id = default_DOT_repair_order.repair_order_id +LEFT OUTER JOIN (SELECT default_DOT_hard_hats.birth_date, + default_DOT_hard_hats.city, + default_DOT_hard_hats.country, + default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.hire_date, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats) + AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + GROUP BY default_DOT_hard_hat.country, default_DOT_hard_hat.city +) AS m0_default_DOT_avg_repair_price +LIMIT 5 +) + +SELECT Sum(avg_repair_price), + city + FROM (SELECT metric_query_0.default_DOT_avg_repair_price AS avg_repair_price, + metric_query_0.country, + metric_query_0.city + FROM metric_query_0 +) + GROUP BY city""" + + assert compare_query_strings(query, expected_query) + + +def test_get_djsql_data_only_multiple_metrics( + client_with_query_service: TestClient, +) -> None: + """ + Test djsql with metric subquery + """ + + query = """ + SELECT + default.avg_repair_price avg_repair_price, + default.total_repair_cost total_cost, + default.hard_hat.country, + default.hard_hat.city + FROM + metrics + GROUP BY + default.hard_hat.country, + default.hard_hat.city + """ + + response = client_with_query_service.get( + "/djsql/data/", + params={"query": query}, + ) + query = response.json()["results"][0]["sql"] + expected_query = """WITH +metric_query_0 AS (SELECT m0_default_DOT_avg_repair_price.default_DOT_avg_repair_price, + m1_default_DOT_total_repair_cost.default_DOT_total_repair_cost, + COALESCE(m0_default_DOT_avg_repair_price.city, m1_default_DOT_total_repair_cost.city) city, + COALESCE(m0_default_DOT_avg_repair_price.country, m1_default_DOT_total_repair_cost.country) country + FROM (SELECT default_DOT_hard_hat.city, + default_DOT_hard_hat.country, + avg(default_DOT_repair_order_details.price) AS default_DOT_avg_repair_price + FROM roads.repair_order_details AS default_DOT_repair_order_details LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders) + AS default_DOT_repair_order ON default_DOT_repair_order_details.repair_order_id = default_DOT_repair_order.repair_order_id +LEFT OUTER JOIN (SELECT default_DOT_hard_hats.city, + default_DOT_hard_hats.country, + default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats) + AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + GROUP BY default_DOT_hard_hat.country, default_DOT_hard_hat.city +) AS m0_default_DOT_avg_repair_price FULL OUTER JOIN (SELECT default_DOT_hard_hat.city, + default_DOT_hard_hat.country, + sum(default_DOT_repair_order_details.price) default_DOT_total_repair_cost + FROM roads.repair_order_details AS default_DOT_repair_order_details LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders) + AS default_DOT_repair_order ON default_DOT_repair_order_details.repair_order_id = default_DOT_repair_order.repair_order_id +LEFT OUTER JOIN (SELECT default_DOT_hard_hats.city, + default_DOT_hard_hats.country, + default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats) + AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + GROUP BY default_DOT_hard_hat.country, default_DOT_hard_hat.city +) AS m1_default_DOT_total_repair_cost ON m0_default_DOT_avg_repair_price.city = m1_default_DOT_total_repair_cost.city AND m0_default_DOT_avg_repair_price.country = m1_default_DOT_total_repair_cost.country + +) + +SELECT metric_query_0.default_DOT_avg_repair_price AS avg_repair_price, + metric_query_0.default_DOT_total_repair_cost AS total_cost, + metric_query_0.country, + metric_query_0.city + FROM metric_query_0""" + + assert compare_query_strings(query, expected_query) + + +def test_get_djsql_metric_table_exception( + client_with_query_service: TestClient, +) -> None: + """ + Test djsql with metric subquery from non `metrics` + """ + + query = """ + SELECT + default.avg_repair_price avg_repair_price, + default.hard_hat.country, + default.hard_hat.city + FROM + oops + GROUP BY + default.hard_hat.country, + default.hard_hat.city + + """ + + response = client_with_query_service.get( + "/djsql/data/", + params={"query": query}, + ) + assert ( + response.json()["message"] + == "Any SELECT referencing a Metric must source from a single unaliased Table named `metrics`." + ) + + +def test_get_djsql_illegal_clause_metric_query( + client_with_query_service: TestClient, +) -> None: + """ + Test djsql with metric subquery from non `metrics` + """ + + query = """ + SELECT + default.avg_repair_price avg_repair_price, + default.hard_hat.country, + default.hard_hat.city + FROM + metrics + GROUP BY + default.hard_hat.country, + default.hard_hat.city + HAVING 5 + """ + + response = client_with_query_service.get( + "/djsql/data/", + params={"query": query}, + ) + assert ( + response.json()["message"] + == "HAVING, LATERAL VIEWS, and SET OPERATIONS are not allowed on `metrics` queries." + ) + + +def test_get_djsql_illegal_column_expression( + client_with_query_service: TestClient, +) -> None: + """ + Test djsql with non col exp in projection + """ + + query = """ + SELECT + default.avg_repair_price avg_repair_price, + default.hard_hat.country, + default.hard_hat.id+5 + FROM + metrics + GROUP BY + default.hard_hat.country, + default.hard_hat.city + """ + + response = client_with_query_service.get( + "/djsql/data/", + params={"query": query}, + ) + assert ( + response.json()["message"] + == "Only direct Columns are allowed in `metrics` queries, found `default.hard_hat.id + 5`." + ) + + +def test_get_djsql_illegal_column( + client_with_query_service: TestClient, +) -> None: + """ + Test djsql with bad col in projection + """ + + query = """ + SELECT + default.avg_repair_price avg_repair_price, + default.hard_hat.country, + default.repair_orders.id + FROM + metrics + GROUP BY + default.hard_hat.country, + default.hard_hat.city + """ + + response = client_with_query_service.get( + "/djsql/data/", + params={"query": query}, + ) + assert ( + response.json()["message"] + == "You can only select direct METRIC nodes or a column from your GROUP BY on `metrics` queries, found `default.repair_orders.id`" + ) + + +def test_get_djsql_illegal_limit( + client_with_query_service: TestClient, +) -> None: + """ + Test djsql with bad limit + """ + + query = """ + SELECT + default.avg_repair_price avg_repair_price + FROM + metrics + GROUP BY + default.hard_hat.country + LIMIT 1+2 + """ + + response = client_with_query_service.get( + "/djsql/data/", + params={"query": query}, + ) + assert ( + response.json()["message"] + == "LIMITs on `metrics` queries can only be integers not `1 + 2`." + ) + + +def test_get_djsql_no_nodes( + client_with_query_service: TestClient, +) -> None: + """ + Test djsql without dj node refs + """ + + query = """ + SELECT 1 + """ + + response = client_with_query_service.get( + "/djsql/data/", + params={"query": query}, + ) + assert response.json()["message"].startswith("Found no dj nodes in query") + + +# def test_djsql_stream( +# client_with_query_service: TestClient, +# ) -> None: +# """ +# Test streaming djsql +# """ +# query = """ +# SELECT default.hard_hat.country, +# default.hard_hat.city +# FROM default.hard_hat +# """ + +# response = client_with_query_service.get( +# "/djsql/stream/", +# params={"query": query}, +# headers={ +# "Accept": "text/event-stream", +# }, +# stream=True, +# ) +# assert response.status_code == 200 diff --git a/datajunction-server/tests/api/engine_test.py b/datajunction-server/tests/api/engine_test.py new file mode 100644 index 000000000..bbca2c9d5 --- /dev/null +++ b/datajunction-server/tests/api/engine_test.py @@ -0,0 +1,148 @@ +""" +Tests for the engine API. +""" + +from fastapi.testclient import TestClient + + +def test_engine_adding_a_new_engine( + client: TestClient, +) -> None: + """ + Test adding an engine + """ + response = client.post( + "/engines/", + json={ + "name": "spark", + "version": "3.3.1", + "dialect": "spark", + }, + ) + data = response.json() + assert response.status_code == 201 + assert data == { + "dialect": "spark", + "name": "spark", + "uri": None, + "version": "3.3.1", + } + + +def test_engine_list( + client: TestClient, +) -> None: + """ + Test listing engines + """ + response = client.post( + "/engines/", + json={ + "name": "spark", + "version": "2.4.4", + "dialect": "spark", + }, + ) + assert response.status_code == 201 + + response = client.post( + "/engines/", + json={ + "name": "spark", + "version": "3.3.0", + "dialect": "spark", + }, + ) + assert response.status_code == 201 + + response = client.post( + "/engines/", + json={ + "name": "spark", + "version": "3.3.1", + "dialect": "spark", + }, + ) + assert response.status_code == 201 + + response = client.get("/engines/") + assert response.status_code == 200 + data = response.json() + assert data == [ + { + "name": "spark", + "uri": None, + "version": "2.4.4", + "dialect": "spark", + }, + { + "name": "spark", + "uri": None, + "version": "3.3.0", + "dialect": "spark", + }, + { + "name": "spark", + "uri": None, + "version": "3.3.1", + "dialect": "spark", + }, + ] + + +def test_engine_get_engine( + client: TestClient, +) -> None: + """ + Test getting an engine + """ + response = client.post( + "/engines/", + json={ + "name": "spark", + "version": "3.3.1", + "dialect": "spark", + }, + ) + assert response.status_code == 201 + + response = client.get( + "/engines/spark/3.3.1", + ) + assert response.status_code == 200 + data = response.json() + assert data == { + "name": "spark", + "uri": None, + "version": "3.3.1", + "dialect": "spark", + } + + +def test_engine_raise_on_engine_already_exists( + client: TestClient, +) -> None: + """ + Test raise on engine already exists + """ + response = client.post( + "/engines/", + json={ + "name": "spark", + "version": "3.3.1", + "dialect": "spark", + }, + ) + assert response.status_code == 201 + + response = client.post( + "/engines/", + json={ + "name": "spark", + "version": "3.3.1", + "dialect": "spark", + }, + ) + assert response.status_code == 409 + data = response.json() + assert data == {"detail": "Engine already exists: `spark` version `3.3.1`"} diff --git a/datajunction-server/tests/api/graphql/__init__.py b/datajunction-server/tests/api/graphql/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/datajunction-server/tests/api/graphql/catalog_test.py b/datajunction-server/tests/api/graphql/catalog_test.py new file mode 100644 index 000000000..31e0aa81c --- /dev/null +++ b/datajunction-server/tests/api/graphql/catalog_test.py @@ -0,0 +1,62 @@ +""" +Tests for the catalog API. +""" + +from fastapi.testclient import TestClient + + +def test_catalog_list( + client: TestClient, +) -> None: + """ + Test listing catalogs + """ + response = client.post( + "/engines/", + json={ + "name": "spark", + "version": "3.3.1", + "dialect": "spark", + }, + ) + + response = client.post( + "/catalogs/", + json={ + "name": "dev", + "engines": [ + { + "name": "spark", + "version": "3.3.1", + "dialect": "spark", + }, + ], + }, + ) + + response = client.post( + "/catalogs/", + json={ + "name": "test", + }, + ) + + response = client.post( + "/catalogs/", + json={ + "name": "prod", + }, + ) + query = """ + { + listCatalogs{ + name + } + } + """ + + response = client.post("/graphql", json={"query": query}) + assert response.status_code == 200 + assert response.json() == { + "data": {"listCatalogs": [{"name": "dev"}, {"name": "test"}, {"name": "prod"}]}, + } diff --git a/datajunction-server/tests/api/graphql/engine_test.py b/datajunction-server/tests/api/graphql/engine_test.py new file mode 100644 index 000000000..b17155885 --- /dev/null +++ b/datajunction-server/tests/api/graphql/engine_test.py @@ -0,0 +1,62 @@ +""" +Tests for the engine API. +""" + +from fastapi.testclient import TestClient + + +def test_engine_list( + client: TestClient, +) -> None: + """ + Test listing engines + """ + response = client.post( + "/engines/", + json={ + "name": "spark", + "version": "2.4.4", + "dialect": "spark", + }, + ) + + response = client.post( + "/engines/", + json={ + "name": "spark", + "version": "3.3.0", + "dialect": "spark", + }, + ) + + response = client.post( + "/engines/", + json={ + "name": "spark", + "version": "3.3.1", + "dialect": "spark", + }, + ) + query = """ + { + listEngines{ + name + uri + version + dialect + } + } + """ + + response = client.post("/graphql", json={"query": query}) + assert response.status_code == 200 + data = response.json() + assert data == { + "data": { + "listEngines": [ + {"name": "spark", "uri": None, "version": "2.4.4", "dialect": "SPARK"}, + {"name": "spark", "uri": None, "version": "3.3.0", "dialect": "SPARK"}, + {"name": "spark", "uri": None, "version": "3.3.1", "dialect": "SPARK"}, + ], + }, + } diff --git a/datajunction-server/tests/api/health_test.py b/datajunction-server/tests/api/health_test.py new file mode 100644 index 000000000..ce74de002 --- /dev/null +++ b/datajunction-server/tests/api/health_test.py @@ -0,0 +1,26 @@ +""" +Tests for the healthcheck API. +""" + +from fastapi.testclient import TestClient +from sqlmodel import Session + + +def test_successful_health(client: TestClient) -> None: + """ + Test ``GET /health/``. + """ + response = client.get("/health/") + data = response.json() + assert data == [{"name": "database", "status": "ok"}] + + +def test_failed_health(session: Session, client: TestClient) -> None: + """ + Test failed healthcheck. + """ + + session.execute = lambda: False + response = client.get("/health/") + data = response.json() + assert data == [{"name": "database", "status": "failed"}] diff --git a/datajunction-server/tests/api/helpers_test.py b/datajunction-server/tests/api/helpers_test.py new file mode 100644 index 000000000..83c6e9edf --- /dev/null +++ b/datajunction-server/tests/api/helpers_test.py @@ -0,0 +1,41 @@ +""" +Tests for API helpers. +""" + +import pytest +from sqlmodel import Session + +from datajunction_server.api import helpers +from datajunction_server.errors import DJException +from datajunction_server.models import NodeRevision +from datajunction_server.models.node import NodeStatus + + +def test_raise_get_node_when_node_does_not_exist(session: Session): + """ + Test raising when a node doesn't exist + """ + with pytest.raises(DJException) as exc_info: + helpers.get_node_by_name(session=session, name="foo") + + assert "A node with name `foo` does not exist." in str(exc_info.value) + + +def test_propagate_valid_status(session: Session): + """ + Test raising when trying to propagate a valid status using an invalid node + """ + invalid_node = NodeRevision( + name="foo", + staus=NodeStatus.INVALID, + ) + with pytest.raises(DJException) as exc_info: + helpers.propagate_valid_status( + session=session, + valid_nodes=[invalid_node], + catalog_id=1, + ) + + assert "Cannot propagate valid status: Node `foo` is not valid" in str( + exc_info.value, + ) diff --git a/datajunction-server/tests/api/history_test.py b/datajunction-server/tests/api/history_test.py new file mode 100644 index 000000000..c9a8cc6a1 --- /dev/null +++ b/datajunction-server/tests/api/history_test.py @@ -0,0 +1,170 @@ +""" +Tests for the history endpoint +""" +from unittest import mock + +from fastapi.testclient import TestClient + +from datajunction_server.models.history import ActivityType, EntityType, History + + +def test_history_hash(): + """ + Test hash comparison of history events + """ + foo1 = History( + id=1, + entity_name="bar", + entity_type=EntityType.NODE, + activity_type=ActivityType.CREATE, + ) + foo2 = History( + id=1, + entity_name="bar", + entity_type=EntityType.NODE, + activity_type=ActivityType.CREATE, + ) + assert hash(foo1) == hash(foo2) + + +def test_get_history_entity(client_with_examples: TestClient): + """ + Test getting history for an entity + """ + response = client_with_examples.get("/history/node/default.repair_orders/") + assert response.ok + history = response.json() + assert len(history) == 1 + entity = history[0] + entity.pop("created_at") + assert history == [ + { + "id": mock.ANY, + "pre": {}, + "post": {}, + "node": "default.repair_orders", + "entity_type": "node", + "entity_name": "default.repair_orders", + "activity_type": "create", + "user": None, + "details": {}, + }, + ] + + +def test_get_history_node(client_with_examples: TestClient): + """ + Test getting history for a node + """ + + response = client_with_examples.get("/history?node=default.repair_order") + assert response.ok + history = response.json() + assert len(history) == 5 + assert history == [ + { + "activity_type": "create", + "node": "default.repair_order", + "created_at": mock.ANY, + "details": {}, + "entity_name": "default.repair_order", + "entity_type": "node", + "id": mock.ANY, + "post": {}, + "pre": {}, + "user": None, + }, + { + "activity_type": "set_attribute", + "node": "default.repair_order", + "created_at": mock.ANY, + "details": { + "attributes": [ + { + "attribute_type_name": "primary_key", + "attribute_type_namespace": "system", + "column_name": "repair_order_id", + }, + ], + }, + "entity_name": None, + "entity_type": "column_attribute", + "id": mock.ANY, + "post": {}, + "pre": {}, + "user": None, + }, + { + "activity_type": "create", + "node": "default.repair_order", + "created_at": mock.ANY, + "details": { + "column": "dispatcher_id", + "dimension": "default.dispatcher", + "dimension_column": "dispatcher_id", + }, + "entity_name": "default.repair_order", + "entity_type": "link", + "id": mock.ANY, + "post": {}, + "pre": {}, + "user": None, + }, + { + "activity_type": "create", + "node": "default.repair_order", + "created_at": mock.ANY, + "details": { + "column": "hard_hat_id", + "dimension": "default.hard_hat", + "dimension_column": "hard_hat_id", + }, + "entity_name": "default.repair_order", + "entity_type": "link", + "id": mock.ANY, + "post": {}, + "pre": {}, + "user": None, + }, + { + "activity_type": "create", + "node": "default.repair_order", + "created_at": mock.ANY, + "details": { + "column": "municipality_id", + "dimension": "default.municipality_dim", + "dimension_column": "municipality_id", + }, + "entity_name": "default.repair_order", + "entity_type": "link", + "id": mock.ANY, + "post": {}, + "pre": {}, + "user": None, + }, + ] + + +def test_get_history_namespace(client_with_examples: TestClient): + """ + Test getting history for a node context + """ + + response = client_with_examples.get("/history/namespace/default") + assert response.ok + history = response.json() + assert len(history) == 1 + assert history == [ + { + "activity_type": "create", + "node": None, + "created_at": mock.ANY, + "details": {}, + "entity_name": "default", + "entity_type": "namespace", + "id": mock.ANY, + "post": {}, + "pre": {}, + "user": None, + }, + ] diff --git a/datajunction-server/tests/api/metrics_test.py b/datajunction-server/tests/api/metrics_test.py new file mode 100644 index 000000000..513c5cf62 --- /dev/null +++ b/datajunction-server/tests/api/metrics_test.py @@ -0,0 +1,1094 @@ +# pylint: disable=too-many-lines +""" +Tests for the metrics API. +""" +from fastapi.testclient import TestClient +from sqlmodel import Session, select + +from datajunction_server.models import AttributeType, ColumnAttribute +from datajunction_server.models.column import Column +from datajunction_server.models.database import Database +from datajunction_server.models.node import Node, NodeRevision, NodeType +from datajunction_server.models.table import Table +from datajunction_server.sql.parsing.types import FloatType, IntegerType, StringType +from tests.sql.utils import compare_query_strings + + +def test_read_metrics(client_with_examples: TestClient) -> None: + """ + Test ``GET /metrics/``. + """ + response = client_with_examples.get("/metrics/") + data = response.json() + + assert response.status_code == 200 + assert len(data) > 10 + + +def test_read_metric(session: Session, client: TestClient) -> None: + """ + Test ``GET /metric/{node_id}/``. + """ + client.get("/attributes/") + dimension_attribute = session.exec( + select(AttributeType).where(AttributeType.name == "dimension"), + ).one() + parent_rev = NodeRevision( + name="parent", + version="1", + tables=[ + Table( + database=Database(name="test", URI="sqlite://"), + table="A", + columns=[ + Column(name="ds", type=StringType()), + Column(name="user_id", type=IntegerType()), + Column(name="foo", type=FloatType()), + ], + ), + ], + columns=[ + Column( + name="ds", + type=StringType(), + attributes=[ColumnAttribute(attribute_type=dimension_attribute)], + ), + Column( + name="user_id", + type=IntegerType(), + attributes=[ColumnAttribute(attribute_type=dimension_attribute)], + ), + Column( + name="foo", + type=FloatType(), + attributes=[ColumnAttribute(attribute_type=dimension_attribute)], + ), + ], + ) + parent_node = Node( + name=parent_rev.name, + namespace="default", + type=NodeType.SOURCE, + current_version="1", + ) + parent_rev.node = parent_node + + child_node = Node( + name="child", + namespace="default", + type=NodeType.METRIC, + current_version="1", + ) + child_rev = NodeRevision( + name=child_node.name, + node=child_node, + version="1", + query="SELECT COUNT(*) FROM parent", + parents=[parent_node], + ) + + session.add(child_rev) + session.commit() + + response = client.get("/metrics/child/") + data = response.json() + + assert response.status_code == 200 + assert data["name"] == "child" + assert data["query"] == "SELECT COUNT(*) FROM parent" + assert data["dimensions"] == [ + {"name": "parent.ds", "type": "string", "path": []}, + {"name": "parent.foo", "type": "float", "path": []}, + {"name": "parent.user_id", "type": "int", "path": []}, + ] + + +def test_read_metrics_errors(session: Session, client: TestClient) -> None: + """ + Test errors on ``GET /metrics/{node_id}/``. + """ + database = Database(name="test", URI="sqlite://") + node = Node( + name="a-metric", + namespace="default", + type=NodeType.TRANSFORM, + current_version="1", + ) + node_revision = NodeRevision( + name=node.name, + node=node, + version="1", + query="SELECT 1 AS col", + ) + session.add(database) + session.add(node_revision) + session.execute("CREATE TABLE my_table (one TEXT)") + session.commit() + + response = client.get("/metrics/foo") + assert response.status_code == 404 + data = response.json() + assert data["message"] == "A node with name `foo` does not exist." + + response = client.get("/metrics/a-metric") + assert response.status_code == 400 + assert response.json() == {"detail": "Not a metric node: `a-metric`"} + + +def test_common_dimensions( + client_with_examples: TestClient, +) -> None: + """ + Test ``GET /metrics/common/dimensions``. + """ + response = client_with_examples.get( + "/metrics/common/dimensions?" + "metric=default.total_repair_order_discounts" + "&metric=default.total_repair_cost", + ) + assert response.status_code == 200 + + assert response.json() == [ + { + "name": "default.dispatcher.company_name", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.dispatcher_id", + ], + "type": "string", + }, + { + "name": "default.dispatcher.dispatcher_id", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.dispatcher_id", + ], + "type": "int", + }, + { + "name": "default.dispatcher.phone", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.dispatcher_id", + ], + "type": "string", + }, + { + "name": "default.hard_hat.address", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.hard_hat_id", + ], + "type": "string", + }, + { + "name": "default.hard_hat.birth_date", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.hard_hat_id", + ], + "type": "timestamp", + }, + { + "name": "default.hard_hat.city", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.hard_hat_id", + ], + "type": "string", + }, + { + "name": "default.hard_hat.contractor_id", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.hard_hat_id", + ], + "type": "int", + }, + { + "name": "default.hard_hat.country", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.hard_hat_id", + ], + "type": "string", + }, + { + "name": "default.hard_hat.first_name", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.hard_hat_id", + ], + "type": "string", + }, + { + "name": "default.hard_hat.hard_hat_id", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.hard_hat_id", + ], + "type": "int", + }, + { + "name": "default.hard_hat.hire_date", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.hard_hat_id", + ], + "type": "timestamp", + }, + { + "name": "default.hard_hat.last_name", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.hard_hat_id", + ], + "type": "string", + }, + { + "name": "default.hard_hat.manager", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.hard_hat_id", + ], + "type": "int", + }, + { + "name": "default.hard_hat.postal_code", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.hard_hat_id", + ], + "type": "string", + }, + { + "name": "default.hard_hat.state", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.hard_hat_id", + ], + "type": "string", + }, + { + "name": "default.hard_hat.title", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.hard_hat_id", + ], + "type": "string", + }, + { + "name": "default.municipality_dim.contact_name", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.municipality_id", + ], + "type": "string", + }, + { + "name": "default.municipality_dim.contact_title", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.municipality_id", + ], + "type": "string", + }, + { + "name": "default.municipality_dim.local_region", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.municipality_id", + ], + "type": "string", + }, + { + "name": "default.municipality_dim.municipality_id", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.municipality_id", + ], + "type": "string", + }, + { + "name": "default.municipality_dim.municipality_type_desc", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.municipality_id", + ], + "type": "string", + }, + { + "name": "default.municipality_dim.municipality_type_id", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.municipality_id", + ], + "type": "string", + }, + { + "name": "default.municipality_dim.state_id", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.municipality_id", + ], + "type": "int", + }, + { + "name": "default.repair_order.dispatched_date", + "path": ["default.repair_order_details.repair_order_id"], + "type": "timestamp", + }, + { + "name": "default.repair_order.dispatcher_id", + "path": ["default.repair_order_details.repair_order_id"], + "type": "int", + }, + { + "name": "default.repair_order.hard_hat_id", + "path": ["default.repair_order_details.repair_order_id"], + "type": "int", + }, + { + "name": "default.repair_order.municipality_id", + "path": ["default.repair_order_details.repair_order_id"], + "type": "string", + }, + { + "name": "default.repair_order.order_date", + "path": ["default.repair_order_details.repair_order_id"], + "type": "timestamp", + }, + { + "name": "default.repair_order.repair_order_id", + "path": ["default.repair_order_details.repair_order_id"], + "type": "int", + }, + { + "name": "default.repair_order.required_date", + "path": ["default.repair_order_details.repair_order_id"], + "type": "timestamp", + }, + { + "name": "default.repair_order_details.repair_order_id", + "path": [], + "type": "int", + }, + { + "name": "default.us_state.state_id", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.hard_hat_id", + "default.hard_hat.state", + ], + "type": "int", + }, + { + "name": "default.us_state.state_name", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.hard_hat_id", + "default.hard_hat.state", + ], + "type": "string", + }, + { + "name": "default.us_state.state_region", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.hard_hat_id", + "default.hard_hat.state", + ], + "type": "int", + }, + { + "name": "default.us_state.state_region_description", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.hard_hat_id", + "default.hard_hat.state", + ], + "type": "string", + }, + { + "name": "default.us_state.state_short", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.hard_hat_id", + "default.hard_hat.state", + ], + "type": "string", + }, + ] + + +def test_no_common_dimensions( + client_with_examples: TestClient, +) -> None: + """ + Test getting common dimensions for metrics that have none in common + """ + response = client_with_examples.get( + "/metrics/common/dimensions?" + "metric=basic.num_comments&metric=default.total_repair_order_discounts" + "&metric=default.total_repair_cost", + ) + assert response.status_code == 200 + assert response.json() == [] + + +def test_raise_common_dimensions_not_a_metric_node( + client_with_examples: TestClient, +) -> None: + """ + Test raising ``GET /metrics/common/dimensions`` when not a metric node + """ + response = client_with_examples.get( + "/metrics/common/dimensions?" + "metric=default.total_repair_order_discounts" + "&metric=default.payment_type", + ) + assert response.status_code == 500 + assert response.json()["message"] == "Not a metric node: default.payment_type" + + +def test_raise_common_dimensions_metric_not_found( + client_with_examples: TestClient, +) -> None: + """ + Test raising ``GET /metrics/common/dimensions`` when metric not found + """ + response = client_with_examples.get( + "/metrics/common/dimensions?metric=default.foo&metric=default.bar", + ) + assert response.status_code == 500 + assert response.json() == { + "message": "Metric node not found: default.foo\nMetric node not found: default.bar", + "errors": [ + { + "code": 203, + "message": "Metric node not found: default.foo", + "debug": None, + "context": "", + }, + { + "code": 203, + "message": "Metric node not found: default.bar", + "debug": None, + "context": "", + }, + ], + "warnings": [], + } + + +def test_get_dimensions(client_with_examples: TestClient): + """ + Testing get dimensions for a metric + """ + response = client_with_examples.get("/metrics/default.avg_repair_price/") + + data = response.json() + assert data["dimensions"] == [ + { + "name": "default.dispatcher.company_name", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.dispatcher_id", + ], + "type": "string", + }, + { + "name": "default.dispatcher.dispatcher_id", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.dispatcher_id", + ], + "type": "int", + }, + { + "name": "default.dispatcher.phone", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.dispatcher_id", + ], + "type": "string", + }, + { + "name": "default.hard_hat.address", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.hard_hat_id", + ], + "type": "string", + }, + { + "name": "default.hard_hat.birth_date", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.hard_hat_id", + ], + "type": "timestamp", + }, + { + "name": "default.hard_hat.city", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.hard_hat_id", + ], + "type": "string", + }, + { + "name": "default.hard_hat.contractor_id", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.hard_hat_id", + ], + "type": "int", + }, + { + "name": "default.hard_hat.country", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.hard_hat_id", + ], + "type": "string", + }, + { + "name": "default.hard_hat.first_name", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.hard_hat_id", + ], + "type": "string", + }, + { + "name": "default.hard_hat.hard_hat_id", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.hard_hat_id", + ], + "type": "int", + }, + { + "name": "default.hard_hat.hire_date", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.hard_hat_id", + ], + "type": "timestamp", + }, + { + "name": "default.hard_hat.last_name", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.hard_hat_id", + ], + "type": "string", + }, + { + "name": "default.hard_hat.manager", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.hard_hat_id", + ], + "type": "int", + }, + { + "name": "default.hard_hat.postal_code", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.hard_hat_id", + ], + "type": "string", + }, + { + "name": "default.hard_hat.state", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.hard_hat_id", + ], + "type": "string", + }, + { + "name": "default.hard_hat.title", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.hard_hat_id", + ], + "type": "string", + }, + { + "name": "default.municipality_dim.contact_name", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.municipality_id", + ], + "type": "string", + }, + { + "name": "default.municipality_dim.contact_title", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.municipality_id", + ], + "type": "string", + }, + { + "name": "default.municipality_dim.local_region", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.municipality_id", + ], + "type": "string", + }, + { + "name": "default.municipality_dim.municipality_id", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.municipality_id", + ], + "type": "string", + }, + { + "name": "default.municipality_dim.municipality_type_desc", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.municipality_id", + ], + "type": "string", + }, + { + "name": "default.municipality_dim.municipality_type_id", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.municipality_id", + ], + "type": "string", + }, + { + "name": "default.municipality_dim.state_id", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.municipality_id", + ], + "type": "int", + }, + { + "name": "default.repair_order.dispatched_date", + "path": ["default.repair_order_details.repair_order_id"], + "type": "timestamp", + }, + { + "name": "default.repair_order.dispatcher_id", + "path": ["default.repair_order_details.repair_order_id"], + "type": "int", + }, + { + "name": "default.repair_order.hard_hat_id", + "path": ["default.repair_order_details.repair_order_id"], + "type": "int", + }, + { + "name": "default.repair_order.municipality_id", + "path": ["default.repair_order_details.repair_order_id"], + "type": "string", + }, + { + "name": "default.repair_order.order_date", + "path": ["default.repair_order_details.repair_order_id"], + "type": "timestamp", + }, + { + "name": "default.repair_order.repair_order_id", + "path": ["default.repair_order_details.repair_order_id"], + "type": "int", + }, + { + "name": "default.repair_order.required_date", + "path": ["default.repair_order_details.repair_order_id"], + "type": "timestamp", + }, + { + "name": "default.repair_order_details.repair_order_id", + "path": [], + "type": "int", + }, + { + "name": "default.us_state.state_id", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.hard_hat_id", + "default.hard_hat.state", + ], + "type": "int", + }, + { + "name": "default.us_state.state_name", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.hard_hat_id", + "default.hard_hat.state", + ], + "type": "string", + }, + { + "name": "default.us_state.state_region", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.hard_hat_id", + "default.hard_hat.state", + ], + "type": "int", + }, + { + "name": "default.us_state.state_region_description", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.hard_hat_id", + "default.hard_hat.state", + ], + "type": "string", + }, + { + "name": "default.us_state.state_short", + "path": [ + "default.repair_order_details.repair_order_id", + "default.repair_order.hard_hat_id", + "default.hard_hat.state", + ], + "type": "string", + }, + ] + + +def test_get_multi_link_dimensions( + client_with_examples: TestClient, +): + """ + In some cases, the same dimension may be linked to different columns on a node. + The returned dimension attributes should the join path between the given dimension + attribute and the original node, in order to help disambiguate the source of the dimension. + """ + response = client_with_examples.get("/metrics/default.avg_user_age/") + assert response.json()["dimensions"] == [ + { + "name": "default.date_dim.dateint", + "path": [ + "default.user_dim.birth_country", + "default.special_country_dim.formation_date", + ], + "type": "int", + }, + { + "name": "default.date_dim.dateint", + "path": [ + "default.user_dim.birth_country", + "default.special_country_dim.last_election_date", + ], + "type": "int", + }, + { + "name": "default.date_dim.dateint", + "path": [ + "default.user_dim.residence_country", + "default.special_country_dim.formation_date", + ], + "type": "int", + }, + { + "name": "default.date_dim.dateint", + "path": [ + "default.user_dim.residence_country", + "default.special_country_dim.last_election_date", + ], + "type": "int", + }, + { + "name": "default.date_dim.day", + "path": [ + "default.user_dim.birth_country", + "default.special_country_dim.formation_date", + ], + "type": "int", + }, + { + "name": "default.date_dim.day", + "path": [ + "default.user_dim.birth_country", + "default.special_country_dim.last_election_date", + ], + "type": "int", + }, + { + "name": "default.date_dim.day", + "path": [ + "default.user_dim.residence_country", + "default.special_country_dim.formation_date", + ], + "type": "int", + }, + { + "name": "default.date_dim.day", + "path": [ + "default.user_dim.residence_country", + "default.special_country_dim.last_election_date", + ], + "type": "int", + }, + { + "name": "default.date_dim.month", + "path": [ + "default.user_dim.birth_country", + "default.special_country_dim.formation_date", + ], + "type": "int", + }, + { + "name": "default.date_dim.month", + "path": [ + "default.user_dim.birth_country", + "default.special_country_dim.last_election_date", + ], + "type": "int", + }, + { + "name": "default.date_dim.month", + "path": [ + "default.user_dim.residence_country", + "default.special_country_dim.formation_date", + ], + "type": "int", + }, + { + "name": "default.date_dim.month", + "path": [ + "default.user_dim.residence_country", + "default.special_country_dim.last_election_date", + ], + "type": "int", + }, + { + "name": "default.date_dim.year", + "path": [ + "default.user_dim.birth_country", + "default.special_country_dim.formation_date", + ], + "type": "int", + }, + { + "name": "default.date_dim.year", + "path": [ + "default.user_dim.birth_country", + "default.special_country_dim.last_election_date", + ], + "type": "int", + }, + { + "name": "default.date_dim.year", + "path": [ + "default.user_dim.residence_country", + "default.special_country_dim.formation_date", + ], + "type": "int", + }, + { + "name": "default.date_dim.year", + "path": [ + "default.user_dim.residence_country", + "default.special_country_dim.last_election_date", + ], + "type": "int", + }, + { + "name": "default.special_country_dim.country_code", + "path": ["default.user_dim.birth_country"], + "type": "string", + }, + { + "name": "default.special_country_dim.country_code", + "path": ["default.user_dim.residence_country"], + "type": "string", + }, + { + "name": "default.special_country_dim.formation_date", + "path": ["default.user_dim.birth_country"], + "type": "int", + }, + { + "name": "default.special_country_dim.formation_date", + "path": ["default.user_dim.residence_country"], + "type": "int", + }, + { + "name": "default.special_country_dim.last_election_date", + "path": ["default.user_dim.birth_country"], + "type": "int", + }, + { + "name": "default.special_country_dim.last_election_date", + "path": ["default.user_dim.residence_country"], + "type": "int", + }, + { + "name": "default.special_country_dim.name", + "path": ["default.user_dim.birth_country"], + "type": "string", + }, + { + "name": "default.special_country_dim.name", + "path": ["default.user_dim.residence_country"], + "type": "string", + }, + {"name": "default.user_dim.age", "path": [], "type": "int"}, + {"name": "default.user_dim.birth_country", "path": [], "type": "string"}, + {"name": "default.user_dim.residence_country", "path": [], "type": "string"}, + {"name": "default.user_dim.user_id", "path": [], "type": "int"}, + ] + + +def test_type_inference_structs(client_with_examples: TestClient): + """ + Testing type resolution for structs select + """ + client_with_examples.post( + "/nodes/source/", + json={ + "columns": [ + { + "name": "counts", + "type": "struct", + }, + ], + "description": "Collection of dreams", + "mode": "published", + "name": "basic.dreams", + "catalog": "public", + "schema_": "basic", + "table": "dreams", + }, + ) + + response = client_with_examples.post( + "/nodes/metric/", + json={ + "query": "SELECT SUM(counts.b) FROM basic.dreams", + "description": "Dream Counts", + "mode": "published", + "name": "basic.dream_count", + }, + ) + response.json() + + +def test_metric_expression_auto_aliased(client_with_examples: TestClient): + """ + Testing that a metric's expression column is automatically aliased + """ + client_with_examples.post( + "/nodes/source/", + json={ + "columns": [ + { + "name": "counts", + "type": "struct", + }, + ], + "description": "Collection of dreams", + "mode": "published", + "name": "basic.dreams", + "catalog": "public", + "schema_": "basic", + "table": "dreams", + }, + ) + + response = client_with_examples.post( + "/nodes/metric/", + json={ + "query": "SELECT SUM(counts.b) + SUM(counts.b) FROM basic.dreams", + "description": "Dream Counts", + "mode": "published", + "name": "basic.dream_count", + }, + ) + assert response.status_code == 201 + assert compare_query_strings( + response.json()["query"], + "SELECT SUM(counts.b) + SUM(counts.b) basic_DOT_dream_count \n FROM basic.dreams\n", + ) + + +def test_raise_on_malformated_expression_alias(client_with_examples: TestClient): + """ + Testing raising when an invalid alias is used for a metric expression + """ + client_with_examples.post( + "/nodes/source/", + json={ + "columns": [ + { + "name": "counts", + "type": "struct", + }, + ], + "description": "Collection of dreams", + "mode": "published", + "name": "basic.dreams", + "catalog": "public", + "schema_": "basic", + "table": "dreams", + }, + ) + + response = client_with_examples.post( + "/nodes/metric/", + json={ + "query": "SELECT SUM(counts.b) as foo FROM basic.dreams", + "description": "Dream Counts", + "mode": "published", + "name": "basic.dream_count", + }, + ) + assert response.status_code == 422 + assert ( + "Invalid Metric. The expression in the projection cannot " + "have alias different from the node name. Got `foo` " + "but expected `basic_DOT_dream_count`" + ) in response.json()["message"] + + +def test_raise_on_multiple_expressions(client_with_examples: TestClient): + """ + Testing raising when there is more than one expression + """ + client_with_examples.post( + "/nodes/source/", + json={ + "columns": [ + { + "name": "counts", + "type": "struct", + }, + ], + "description": "Collection of dreams", + "mode": "published", + "name": "basic.dreams", + "catalog": "public", + "schema_": "basic", + "table": "dreams", + }, + ) + + response = client_with_examples.post( + "/nodes/metric/", + json={ + "query": "SELECT SUM(counts.b), COUNT(counts.b) FROM basic.dreams", + "description": "Dream Counts", + "mode": "published", + "name": "basic.dream_count", + }, + ) + assert response.status_code == 400 + assert ( + "Metric queries can only have a single expression, found 2" + ) in response.json()["message"] diff --git a/datajunction-server/tests/api/namespaces_test.py b/datajunction-server/tests/api/namespaces_test.py new file mode 100644 index 000000000..4d47a3a48 --- /dev/null +++ b/datajunction-server/tests/api/namespaces_test.py @@ -0,0 +1,251 @@ +""" +Tests for the namespaces API. +""" +from fastapi.testclient import TestClient + + +def test_list_all_namespaces(client_with_examples: TestClient) -> None: + """ + Test ``GET /namespaces/``. + """ + response = client_with_examples.get("/namespaces/") + assert response.ok + assert set(response.json()) == { + "default", + "foo.bar", + "basic", + "basic.source", + "basic.transform", + "basic.dimension", + "dbt.source", + "dbt.source.jaffle_shop", + "dbt.transform", + "dbt.dimension", + "dbt.source.stripe", + } + + +def test_list_nodes_by_namespace(client_with_examples: TestClient) -> None: + """ + Test ``GET /namespaces/{namespace}/``. + """ + response = client_with_examples.get("/namespaces/basic.source/") + assert response.ok + assert set(response.json()) == {"basic.source.users", "basic.source.comments"} + + response = client_with_examples.get("/namespaces/basic/") + assert response.ok + assert set(response.json()) == { + "basic.source.users", + "basic.dimension.users", + "basic.source.comments", + "basic.dimension.countries", + "basic.transform.country_agg", + "basic.num_comments", + "basic.num_users", + "basic.murals", + "basic.patches", + "basic.corrected_patches", + "basic.paint_colors_trino", + "basic.paint_colors_spark", + "basic.avg_luminosity_patches", + } + + response = client_with_examples.get("/namespaces/basic/?type_=dimension") + assert response.ok + assert set(response.json()) == { + "basic.dimension.users", + "basic.dimension.countries", + "basic.paint_colors_spark", + "basic.paint_colors_trino", + } + + response = client_with_examples.get("/namespaces/basic/?type_=source") + assert response.ok + assert set(response.json()) == { + "basic.source.comments", + "basic.murals", + "basic.source.users", + "basic.patches", + } + + +def test_deactivate_namespaces(client_with_examples: TestClient) -> None: + """ + Test ``DELETE /namespaces/{namespace}``. + """ + # Cannot deactivate if there are nodes under the namespace + response = client_with_examples.delete("/namespaces/foo.bar/?cascade=false") + assert response.json() == { + "message": "Cannot deactivate node namespace `foo.bar` as there are still " + "active nodes under that namespace.", + } + + # Can deactivate with cascade + response = client_with_examples.delete("/namespaces/foo.bar/?cascade=true") + assert response.json() == { + "message": "Namespace `foo.bar` has been deactivated. The following nodes " + "have also been deactivated: foo.bar.repair_orders,foo.bar.repair_order_details," + "foo.bar.repair_type,foo.bar.contractors,foo.bar.municipality_municipality_type," + "foo.bar.municipality_type,foo.bar.municipality,foo.bar.dispatchers,foo.bar.hard_hats," + "foo.bar.hard_hat_state,foo.bar.us_states,foo.bar.us_region,foo.bar.repair_order," + "foo.bar.contractor,foo.bar.hard_hat,foo.bar.local_hard_hats,foo.bar.us_state," + "foo.bar.dispatcher,foo.bar.municipality_dim,foo.bar.num_repair_orders," + "foo.bar.avg_repair_price,foo.bar.total_repair_cost,foo.bar.avg_length_of_employment," + "foo.bar.total_repair_order_discounts,foo.bar.avg_repair_order_discounts," + "foo.bar.avg_time_to_dispatch", + } + + # Check that the namespace is no longer listed + response = client_with_examples.get("/namespaces/") + assert response.ok + assert response.json() == [ + "default", + "basic", + "basic.source", + "basic.transform", + "basic.dimension", + "dbt.source", + "dbt.source.jaffle_shop", + "dbt.transform", + "dbt.dimension", + "dbt.source.stripe", + ] + + response = client_with_examples.delete("/namespaces/foo.bar/?cascade=false") + assert response.json()["message"] == "Namespace `foo.bar` is already deactivated." + + # Try restoring + response = client_with_examples.post("/namespaces/foo.bar/restore/") + assert response.json() == { + "message": "Namespace `foo.bar` has been restored.", + } + + # Check that the namespace is back + response = client_with_examples.get("/namespaces/") + assert response.ok + assert response.json() == [ + "default", + "foo.bar", + "basic", + "basic.source", + "basic.transform", + "basic.dimension", + "dbt.source", + "dbt.source.jaffle_shop", + "dbt.transform", + "dbt.dimension", + "dbt.source.stripe", + ] + + # Check that nodes in the namespace remain deactivated + response = client_with_examples.get("/namespaces/foo.bar/") + assert response.ok + assert response.json() == [] + + # Restore with cascade=true should also restore all the nodes + client_with_examples.delete("/namespaces/foo.bar/?cascade=false") + response = client_with_examples.post("/namespaces/foo.bar/restore/?cascade=true") + assert response.json() == { + "message": "Namespace `foo.bar` has been restored. The following nodes have " + "also been restored: foo.bar.avg_length_of_employment,foo.bar.avg_repair_order_discounts," + "foo.bar.avg_repair_price,foo.bar.avg_time_to_dispatch,foo.bar.contractor," + "foo.bar.contractors,foo.bar.dispatcher,foo.bar.dispatchers,foo.bar.hard_hat,foo.bar." + "hard_hat_state,foo.bar.hard_hats,foo.bar.local_hard_hats,foo.bar.municipality,foo.bar." + "municipality_dim,foo.bar.municipality_municipality_type,foo.bar.municipality_type,foo.bar" + ".num_repair_orders,foo.bar.repair_order,foo.bar.repair_order_details,foo.bar.repair_orders" + ",foo.bar.repair_type,foo.bar.total_repair_cost,foo.bar.total_repair_order_discounts," + "foo.bar.us_region,foo.bar.us_state,foo.bar.us_states", + } + # Calling restore again will raise + response = client_with_examples.post("/namespaces/foo.bar/restore/?cascade=true") + assert ( + response.json()["message"] + == "Node namespace `foo.bar` already exists and is active." + ) + + # Check that nodes in the namespace are restored + response = client_with_examples.get("/namespaces/foo.bar/") + assert response.ok + assert response.json() == [ + "foo.bar.repair_orders", + "foo.bar.repair_order_details", + "foo.bar.repair_type", + "foo.bar.contractors", + "foo.bar.municipality_municipality_type", + "foo.bar.municipality_type", + "foo.bar.municipality", + "foo.bar.dispatchers", + "foo.bar.hard_hats", + "foo.bar.hard_hat_state", + "foo.bar.us_states", + "foo.bar.us_region", + "foo.bar.repair_order", + "foo.bar.contractor", + "foo.bar.hard_hat", + "foo.bar.local_hard_hats", + "foo.bar.us_state", + "foo.bar.dispatcher", + "foo.bar.municipality_dim", + "foo.bar.num_repair_orders", + "foo.bar.avg_repair_price", + "foo.bar.total_repair_cost", + "foo.bar.avg_length_of_employment", + "foo.bar.total_repair_order_discounts", + "foo.bar.avg_repair_order_discounts", + "foo.bar.avg_time_to_dispatch", + ] + + response = client_with_examples.get("/history/namespace/foo.bar/") + assert [ + (activity["activity_type"], activity["details"]) for activity in response.json() + ] == [ + ("create", {}), + ( + "delete", + { + "message": "Namespace `foo.bar` has been deactivated. The following nodes " + "have also been deactivated: " + "foo.bar.repair_orders,foo.bar.repair_order_details,foo.bar.repair_type," + "foo.bar.contractors,foo.bar.municipality_municipality_type,foo.bar.munic" + "ipality_type,foo.bar.municipality,foo.bar.dispatchers,foo.bar.hard_hats," + "foo.bar.hard_hat_state,foo.bar.us_states,foo.bar.us_region,foo.bar.repair_order," + "foo.bar.contractor,foo.bar.hard_hat,foo.bar.local_hard_hats,foo.bar.us_state," + "foo.bar.dispatcher,foo.bar.municipality_dim,foo.bar.num_repair_orders," + "foo.bar.avg_repair_price,foo.bar.total_repair_cost,foo.bar." + "avg_length_of_employment,foo.bar.total_repair_order_discounts,foo.bar." + "avg_repair_order_discounts,foo.bar.avg_time_to_dispatch", + }, + ), + ("restore", {"message": "Namespace `foo.bar` has been restored."}), + ("delete", {"message": "Namespace `foo.bar` has been deactivated."}), + ( + "restore", + { + "message": "Namespace `foo.bar` has been restored. The following nodes have " + "also been restored: " + "foo.bar.avg_length_of_employment,foo.bar.avg_repair_order_discounts,foo.bar." + "avg_repair_price,foo.bar.avg_time_to_dispatch,foo.bar.contractor," + "foo.bar.contractors,foo.bar.dispatcher,foo.bar.dispatchers,foo.bar.hard_hat," + "foo.bar.hard_hat_state,foo.bar.hard_hats,foo.bar.local_hard_hats,foo.bar." + "municipality,foo.bar.municipality_dim,foo.bar.municipality_municipality_type," + "foo.bar.municipality_type,foo.bar.num_repair_orders,foo.bar.repair_order," + "foo.bar.repair_order_details,foo.bar.repair_orders,foo.bar.repair_type," + "foo.bar.total_repair_cost,foo.bar.total_repair_order_discounts,foo.bar.us_region," + "foo.bar.us_state,foo.bar.us_states", + }, + ), + ] + + response = client_with_examples.get( + "/history?node=foo.bar.avg_length_of_employment", + ) + assert [ + (activity["activity_type"], activity["details"]) for activity in response.json() + ] == [ + ("create", {}), + ("status_change", {"upstream_node": "foo.bar.hard_hats"}), + ("delete", {"message": "Cascaded from deactivating namespace `foo.bar`"}), + ("restore", {"message": "Cascaded from restoring namespace `foo.bar`"}), + ("status_change", {"upstream_node": "foo.bar.hard_hats"}), + ] diff --git a/datajunction-server/tests/api/nodes_test.py b/datajunction-server/tests/api/nodes_test.py new file mode 100644 index 000000000..6a6feee3c --- /dev/null +++ b/datajunction-server/tests/api/nodes_test.py @@ -0,0 +1,4041 @@ +# pylint: disable=too-many-lines +""" +Tests for the nodes API. +""" +import re +from typing import Any, Dict +from unittest import mock + +import pytest +from fastapi.testclient import TestClient +from sqlmodel import Session + +from datajunction_server.internal.materializations import decompose_expression +from datajunction_server.models import Database, Table +from datajunction_server.models.column import Column +from datajunction_server.models.node import Node, NodeRevision, NodeStatus, NodeType +from datajunction_server.sql.parsing import ast, types +from datajunction_server.sql.parsing.types import IntegerType, StringType, TimestampType +from tests.sql.utils import compare_query_strings + + +def materialization_compare(response, expected): + """Compares two materialization lists of json + configs paying special attention to query comparison""" + for materialization_response, materialization_expected in zip(response, expected): + assert compare_query_strings( + materialization_response["config"]["query"], + materialization_expected["config"]["query"], + ) + del materialization_response["config"]["query"] + del materialization_expected["config"]["query"] + assert materialization_response == materialization_expected + + +def test_read_node(client_with_examples: TestClient) -> None: + """ + Test ``GET /nodes/{node_id}``. + """ + response = client_with_examples.get("/nodes/default.repair_orders/") + data = response.json() + + assert response.status_code == 200 + assert data["version"] == "v1.0" + assert data["node_id"] == 1 + assert data["node_revision_id"] == 1 + assert data["type"] == "source" + + response = client_with_examples.get("/nodes/default.nothing/") + data = response.json() + + assert response.status_code == 404 + assert data["message"] == "A node with name `default.nothing` does not exist." + + +def test_read_nodes(session: Session, client: TestClient) -> None: + """ + Test ``GET /nodes/``. + """ + node1 = Node( + name="not-a-metric", + type=NodeType.SOURCE, + current_version="1", + ) + node_rev1 = NodeRevision( + node=node1, + version="1", + name=node1.name, + type=node1.type, + ) + node2 = Node( + name="also-not-a-metric", + type=NodeType.TRANSFORM, + current_version="1", + ) + node_rev2 = NodeRevision( + name=node2.name, + node=node2, + version="1", + query="SELECT 42 AS answer", + type=node2.type, + columns=[ + Column(name="answer", type=IntegerType()), + ], + ) + node3 = Node(name="a-metric", type=NodeType.METRIC, current_version="1") + node_rev3 = NodeRevision( + name=node3.name, + node=node3, + version="1", + query="SELECT COUNT(*) FROM my_table", + columns=[ + Column(name="_col0", type=IntegerType()), + ], + type=node3.type, + ) + session.add(node_rev1) + session.add(node_rev2) + session.add(node_rev3) + session.commit() + + response = client.get("/nodes/") + data = response.json() + + assert response.status_code == 200 + assert len(data) == 3 + assert set(data) == {"not-a-metric", "also-not-a-metric", "a-metric"} + + response = client.get("/nodes?node_type=metric") + data = response.json() + + assert response.status_code == 200 + assert len(data) == 1 + assert set(data) == {"a-metric"} + + +class TestNodeCRUD: # pylint: disable=too-many-public-methods + """ + Test node CRUD + """ + + @pytest.fixture + def create_dimension_node_payload(self) -> Dict[str, Any]: + """ + Payload for creating a dimension node. + """ + + return { + "description": "Country dimension", + "query": "SELECT country, COUNT(1) AS user_cnt " + "FROM basic.source.users GROUP BY country", + "mode": "published", + "name": "default.countries", + "primary_key": ["country"], + } + + @pytest.fixture + def create_invalid_transform_node_payload(self) -> Dict[str, Any]: + """ + Payload for creating a transform node. + """ + + return { + "name": "default.country_agg", + "query": "SELECT country, COUNT(DISTINCT id) AS num_users FROM comments", + "mode": "published", + "description": "Distinct users per country", + "columns": [ + {"name": "country", "type": "string"}, + {"name": "num_users", "type": "int"}, + ], + } + + @pytest.fixture + def create_transform_node_payload(self) -> Dict[str, Any]: + """ + Payload for creating a transform node. + """ + + return { + "name": "default.country_agg", + "query": "SELECT country, COUNT(DISTINCT id) AS num_users FROM basic.source.users", + "mode": "published", + "description": "Distinct users per country", + "columns": [ + {"name": "country", "type": "string"}, + {"name": "num_users", "type": "int"}, + ], + } + + @pytest.fixture + def database(self, session: Session) -> Database: + """ + A database fixture. + """ + + database = Database(name="postgres", URI="postgres://") + session.add(database) + session.commit() + return database + + @pytest.fixture + def source_node(self, session: Session, database: Database) -> Node: + """ + A source node fixture. + """ + + table = Table( + database=database, + table="A", + columns=[ + Column(name="ds", type=StringType()), + Column(name="user_id", type=IntegerType()), + ], + ) + node = Node( + name="basic.source.users", + type=NodeType.SOURCE, + current_version="1", + ) + node_revision = NodeRevision( + node=node, + name=node.name, + type=node.type, + version="1", + tables=[table], + columns=[ + Column(name="id", type=IntegerType()), + Column(name="full_name", type=StringType()), + Column(name="age", type=IntegerType()), + Column(name="country", type=StringType()), + Column(name="gender", type=StringType()), + Column(name="preferred_language", type=StringType()), + ], + ) + session.add(node_revision) + session.commit() + return node + + def test_create_dimension_without_catalog( + self, + client_with_examples: TestClient, + ): + """ + Test that creating a dimension that's purely query-based and therefore + doesn't reference a catalog works. + """ + response = client_with_examples.post( + "/nodes/dimension/", + json={ + "description": "Title", + "query": ( + "SELECT 0 AS title_code, 'Agha' AS title " + "UNION ALL SELECT 1, 'Abbot' " + "UNION ALL SELECT 2, 'Akhoond' " + "UNION ALL SELECT 3, 'Apostle'" + ), + "mode": "published", + "name": "default.title", + "primary_key": ["title"], + }, + ) + assert response.json()["columns"] == [ + {"attributes": [], "dimension": None, "name": "title_code", "type": "int"}, + { + "attributes": [ + {"attribute_type": {"name": "primary_key", "namespace": "system"}}, + ], + "dimension": None, + "name": "title", + "type": "string", + }, + ] + + # Link the dimension to a column on the source node + response = client_with_examples.post( + "/nodes/default.hard_hats/columns/title/" + "?dimension=default.title&dimension_column=title", + ) + assert response.ok + response = client_with_examples.get("/nodes/default.hard_hats/") + assert { + "attributes": [], + "dimension": {"name": "default.title"}, + "name": "title", + "type": "string", + } in response.json()["columns"] + + def test_deleting_node( + self, + client_with_examples: TestClient, + ): + """ + Test deleting a node + """ + # Delete a node + response = client_with_examples.delete("/nodes/basic.source.users/") + assert response.status_code == 200 + # Check that then retrieving the node returns an error + response = client_with_examples.get("/nodes/basic.source.users/") + assert not response.ok + assert response.json() == { + "message": "A node with name `basic.source.users` does not exist.", + "errors": [], + "warnings": [], + } + # All downstream nodes should be invalid + expected_downstreams = [ + "basic.dimension.users", + "basic.transform.country_agg", + "basic.dimension.countries", + "basic.num_users", + ] + for downstream in expected_downstreams: + response = client_with_examples.get(f"/nodes/{downstream}/") + assert response.json()["status"] == NodeStatus.INVALID + + # The downstreams' status change should be recorded in their histories + response = client_with_examples.get(f"/history?node={downstream}") + assert [ + (activity["pre"], activity["post"], activity["details"]) + for activity in response.json() + if activity["activity_type"] == "status_change" + ] == [ + ( + {"status": "valid"}, + {"status": "invalid"}, + {"upstream_node": "basic.source.users"}, + ), + ] + + # Trying to create the node again should work. + response = client_with_examples.post( + "/nodes/source/", + json={ + "name": "basic.source.users", + "description": "A user table", + "columns": [ + {"name": "id", "type": "int"}, + {"name": "full_name", "type": "string"}, + {"name": "age", "type": "int"}, + {"name": "country", "type": "string"}, + {"name": "gender", "type": "string"}, + {"name": "preferred_language", "type": "string"}, + {"name": "secret_number", "type": "float"}, + {"name": "created_at", "type": "timestamp"}, + {"name": "post_processing_timestamp", "type": "timestamp"}, + ], + "mode": "published", + "catalog": "public", + "schema_": "basic", + "table": "dim_users", + }, + ) + assert response.ok + + # The deletion action should be recorded in the node's history + response = client_with_examples.get("/history?node=basic.source.users") + history = response.json() + assert history == [ + { + "activity_type": "create", + "node": "basic.source.users", + "created_at": mock.ANY, + "details": {}, + "entity_name": "basic.source.users", + "entity_type": "node", + "id": mock.ANY, + "post": {}, + "pre": {}, + "user": None, + }, + { + "activity_type": "delete", + "node": "basic.source.users", + "created_at": mock.ANY, + "details": {}, + "entity_name": "basic.source.users", + "entity_type": "node", + "id": mock.ANY, + "post": {}, + "pre": {}, + "user": None, + }, + { + "id": mock.ANY, + "entity_type": "node", + "entity_name": "basic.source.users", + "node": "basic.source.users", + "activity_type": "update", + "user": None, + "pre": {}, + "post": {}, + "details": {"version": "v2.0"}, + "created_at": mock.ANY, + }, + { + "id": mock.ANY, + "entity_type": "node", + "entity_name": "basic.source.users", + "node": "basic.source.users", + "activity_type": "restore", + "user": None, + "pre": {}, + "post": {}, + "details": {}, + "created_at": mock.ANY, + }, + ] + + def test_deleting_source_upstream_from_metric( + self, + client: TestClient, + ): + """ + Test deleting a source that's upstream from a metric + """ + response = client.post("/catalogs/", json={"name": "warehouse"}) + assert response.ok + response = client.post("/namespaces/default/") + assert response.ok + response = client.post( + "/nodes/source/", + json={ + "name": "default.users", + "description": "A user table", + "columns": [ + {"name": "id", "type": "int"}, + {"name": "full_name", "type": "string"}, + {"name": "age", "type": "int"}, + {"name": "country", "type": "string"}, + {"name": "gender", "type": "string"}, + {"name": "preferred_language", "type": "string"}, + {"name": "secret_number", "type": "float"}, + {"name": "created_at", "type": "timestamp"}, + {"name": "post_processing_timestamp", "type": "timestamp"}, + ], + "mode": "published", + "catalog": "warehouse", + "schema_": "db", + "table": "users", + }, + ) + assert response.ok + response = client.post( + "/nodes/metric/", + json={ + "description": "Total number of users", + "query": "SELECT COUNT(DISTINCT id) FROM default.users", + "mode": "published", + "name": "default.num_users", + }, + ) + assert response.ok + # Delete the source node + response = client.delete("/nodes/default.users/") + assert response.ok + # The downstream metric should have an invalid status + assert ( + client.get("/nodes/default.num_users/").json()["status"] + == NodeStatus.INVALID + ) + response = client.get("/history?node=default.num_users") + assert [ + (activity["pre"], activity["post"], activity["details"]) + for activity in response.json() + if activity["activity_type"] == "status_change" + ] == [ + ( + {"status": "valid"}, + {"status": "invalid"}, + {"upstream_node": "default.users"}, + ), + ] + + # Restore the source node + response = client.post("/nodes/default.users/restore/") + assert response.ok + # Retrieving the restored node should work + response = client.get("/nodes/default.users/") + assert response.ok + # The downstream metric should have been changed to valid + response = client.get("/nodes/default.num_users/") + assert response.json()["status"] == NodeStatus.VALID + # Check activity history of downstream metric + response = client.get("/history?node=default.num_users") + assert [ + (activity["pre"], activity["post"], activity["details"]) + for activity in response.json() + if activity["activity_type"] == "status_change" + ] == [ + ( + {"status": "valid"}, + {"status": "invalid"}, + {"upstream_node": "default.users"}, + ), + ( + {"status": "invalid"}, + {"status": "valid"}, + {"upstream_node": "default.users"}, + ), + ] + + def test_deleting_transform_upstream_from_metric( + self, + client: TestClient, + ): + """ + Test deleting a transform that's upstream from a metric + """ + response = client.post("/catalogs/", json={"name": "warehouse"}) + assert response.ok + response = client.post("/namespaces/default/") + assert response.ok + response = client.post( + "/nodes/source/", + json={ + "name": "default.users", + "description": "A user table", + "columns": [ + {"name": "id", "type": "int"}, + {"name": "full_name", "type": "string"}, + {"name": "age", "type": "int"}, + {"name": "country", "type": "string"}, + {"name": "gender", "type": "string"}, + {"name": "preferred_language", "type": "string"}, + {"name": "secret_number", "type": "float"}, + {"name": "created_at", "type": "timestamp"}, + {"name": "post_processing_timestamp", "type": "timestamp"}, + ], + "mode": "published", + "catalog": "warehouse", + "schema_": "db", + "table": "users", + }, + ) + assert response.ok + response = client.post( + "/nodes/transform/", + json={ + "name": "default.us_users", + "description": "US users", + "query": """ + SELECT + id, + full_name, + age, + country, + gender, + preferred_language, + secret_number, + created_at, + post_processing_timestamp + FROM default.users + WHERE country = 'US' + """, + "mode": "published", + }, + ) + assert response.ok + response = client.post( + "/nodes/metric/", + json={ + "description": "Total number of US users", + "query": "SELECT COUNT(DISTINCT id) FROM default.us_users", + "mode": "published", + "name": "default.num_us_users", + }, + ) + assert response.ok + # Create an invalid draft downstream node + # so we can test that it stays invalid + # when the upstream node is restored + response = client.post( + "/nodes/metric/", + json={ + "description": "An invalid node downstream of default.us_users", + "query": "SELECT COUNT(DISTINCT non_existent_column) FROM default.us_users", + "mode": "draft", + "name": "default.invalid_metric", + }, + ) + assert response.ok + response = client.get("/nodes/default.invalid_metric/") + assert response.ok + assert response.json()["status"] == NodeStatus.INVALID + # Delete the transform node + response = client.delete("/nodes/default.us_users/") + assert response.ok + # Retrieving the deleted node should respond that the node doesn't exist + assert client.get("/nodes/default.us_users/").json()["message"] == ( + "A node with name `default.us_users` does not exist." + ) + # The downstream metrics should have an invalid status + assert ( + client.get("/nodes/default.num_us_users/").json()["status"] + == NodeStatus.INVALID + ) + assert ( + client.get("/nodes/default.invalid_metric/").json()["status"] + == NodeStatus.INVALID + ) + + # Check history of downstream metrics + response = client.get("/history?node=default.num_us_users") + assert [ + (activity["pre"], activity["post"], activity["details"]) + for activity in response.json() + if activity["activity_type"] == "status_change" + ] == [ + ( + {"status": "valid"}, + {"status": "invalid"}, + {"upstream_node": "default.us_users"}, + ), + ] + # No change recorded here because the metric was already invalid + response = client.get("/history?node=default.invalid_metric") + assert [ + (activity["pre"], activity["post"]) + for activity in response.json() + if activity["activity_type"] == "status_change" + ] == [] + + # Restore the transform node + response = client.post("/nodes/default.us_users/restore/") + assert response.ok + # Retrieving the restored node should work + response = client.get("/nodes/default.us_users/") + assert response.ok + # Check history of the restored node + response = client.get("/history?node=default.us_users") + history = response.json() + assert [ + (activity["activity_type"], activity["entity_type"]) for activity in history + ] == [("create", "node"), ("delete", "node"), ("restore", "node")] + + # This downstream metric should have been changed to valid + response = client.get("/nodes/default.num_us_users/") + assert response.json()["status"] == NodeStatus.VALID + # Check history of downstream metric + response = client.get("/history?node=default.num_us_users") + assert [ + (activity["pre"], activity["post"], activity["details"]) + for activity in response.json() + if activity["activity_type"] == "status_change" + ] == [ + ( + {"status": "valid"}, + {"status": "invalid"}, + {"upstream_node": "default.us_users"}, + ), + ( + {"status": "invalid"}, + {"status": "valid"}, + {"upstream_node": "default.us_users"}, + ), + ] + + # The other downstream metric should have remained invalid + response = client.get("/nodes/default.invalid_metric/") + assert response.json()["status"] == NodeStatus.INVALID + # Check history of downstream metric + response = client.get("/history?node=default.invalid_metric") + assert [ + (activity["pre"], activity["post"]) + for activity in response.json() + if activity["activity_type"] == "status_change" + ] == [] + + def test_deleting_linked_dimension( + self, + client: TestClient, + ): + """ + Test deleting a dimension that's linked to columns on other nodes + """ + response = client.post("/catalogs/", json={"name": "warehouse"}) + assert response.ok + response = client.post("/namespaces/default/") + assert response.ok + response = client.post( + "/nodes/source/", + json={ + "name": "default.users", + "description": "A user table", + "columns": [ + {"name": "id", "type": "int"}, + {"name": "full_name", "type": "string"}, + {"name": "age", "type": "int"}, + {"name": "country", "type": "string"}, + {"name": "gender", "type": "string"}, + {"name": "preferred_language", "type": "string"}, + {"name": "secret_number", "type": "float"}, + {"name": "created_at", "type": "timestamp"}, + {"name": "post_processing_timestamp", "type": "timestamp"}, + ], + "mode": "published", + "catalog": "warehouse", + "schema_": "db", + "table": "users", + }, + ) + assert response.ok + response = client.post( + "/nodes/dimension/", + json={ + "name": "default.us_users", + "description": "US users", + "query": """ + SELECT + id, + full_name, + age, + country, + gender, + preferred_language, + secret_number, + created_at, + post_processing_timestamp + FROM default.users + WHERE country = 'US' + """, + "primary_key": ["id"], + "mode": "published", + }, + ) + assert response.ok + response = client.post( + "/nodes/source/", + json={ + "name": "default.messages", + "description": "A table of user messages", + "columns": [ + {"name": "id", "type": "int"}, + {"name": "user_id", "type": "int"}, + {"name": "message", "type": "int"}, + {"name": "posted_at", "type": "timestamp"}, + ], + "mode": "published", + "catalog": "warehouse", + "schema_": "db", + "table": "messages", + }, + ) + assert response.ok + # Create a metric on the source node + response = client.post( + "/nodes/metric/", + json={ + "description": "Total number of user messages", + "query": "SELECT COUNT(DISTINCT id) FROM default.messages", + "mode": "published", + "name": "default.num_messages", + }, + ) + assert response.ok + + # Create a metric on the source node w/ bound dimensions + response = client.post( + "/nodes/metric/", + json={ + "description": "Total number of user messages by id", + "query": "SELECT COUNT(DISTINCT id) FROM default.messages", + "mode": "published", + "name": "default.num_messages_id", + "required_dimensions": ["default.messages.id"], + }, + ) + assert response.ok + + # Create a metric w/ bound dimensions that to not exist + with pytest.raises(Exception) as exc: + response = client.post( + "/nodes/metric/", + json={ + "description": "Total number of user messages by id", + "query": "SELECT COUNT(DISTINCT id) FROM default.messages", + "mode": "published", + "name": "default.num_messages_id", + "required_dimensions": ["default.nothin.id"], + }, + ) + assert "required dimensions that are not on parent nodes" in str(exc) + + # Create a metric on the source node w/ an invalid bound dimension + response = client.post( + "/nodes/metric/", + json={ + "description": "Total number of user messages by id", + "query": "SELECT COUNT(DISTINCT id) FROM default.messages", + "mode": "published", + "name": "default.num_messages_id_invalid_dimension", + "required_dimensions": ["default.messages.foo"], + }, + ) + assert response.status_code == 400 + assert response.json() == { + "message": "Node definition contains references to " + "columns as required dimensions that are not on parent nodes.", + "errors": [ + { + "code": 206, + "message": "Node definition contains references to columns " + "as required dimensions that are not on parent nodes.", + "debug": {"invalid_required_dimensions": ["default.messages.foo"]}, + "context": "", + }, + ], + "warnings": [], + } + + # Link the dimension to a column on the source node + response = client.post( + "/nodes/default.messages/columns/user_id/" + "?dimension=default.us_users&dimension_column=id", + ) + assert response.ok + # The dimension's attributes should now be available to the metric + response = client.get("/metrics/default.num_messages/") + assert response.ok + assert response.json()["dimensions"] == [ + {"name": "default.messages.user_id", "path": [], "type": "int"}, + { + "name": "default.us_users.age", + "path": ["default.messages.user_id"], + "type": "int", + }, + { + "name": "default.us_users.country", + "path": ["default.messages.user_id"], + "type": "string", + }, + { + "name": "default.us_users.created_at", + "path": ["default.messages.user_id"], + "type": "timestamp", + }, + { + "name": "default.us_users.full_name", + "path": ["default.messages.user_id"], + "type": "string", + }, + { + "name": "default.us_users.gender", + "path": ["default.messages.user_id"], + "type": "string", + }, + { + "name": "default.us_users.id", + "path": ["default.messages.user_id"], + "type": "int", + }, + { + "name": "default.us_users.post_processing_timestamp", + "path": ["default.messages.user_id"], + "type": "timestamp", + }, + { + "name": "default.us_users.preferred_language", + "path": ["default.messages.user_id"], + "type": "string", + }, + { + "name": "default.us_users.secret_number", + "path": ["default.messages.user_id"], + "type": "float", + }, + ] + + # Check history of the node with column dimension link + response = client.get( + "/history?node=default.messages", + ) + history = response.json() + assert [ + (activity["activity_type"], activity["entity_type"]) for activity in history + ] == [("create", "node"), ("create", "link")] + + # Delete the dimension node + response = client.delete("/nodes/default.us_users/") + assert response.ok + # Retrieving the deleted node should respond that the node doesn't exist + assert client.get("/nodes/default.us_users/").json()["message"] == ( + "A node with name `default.us_users` does not exist." + ) + # The deleted dimension's attributes should no longer be available to the metric + response = client.get("/metrics/default.num_messages/") + assert response.ok + assert [ + {"path": [], "name": "default.messages.user_id", "type": "int"}, + ] == response.json()["dimensions"] + # The metric should still be VALID + response = client.get("/nodes/default.num_messages/") + assert response.json()["status"] == NodeStatus.VALID + # Restore the dimension node + response = client.post("/nodes/default.us_users/restore/") + assert response.ok + # Retrieving the restored node should work + response = client.get("/nodes/default.us_users/") + assert response.ok + # The dimension's attributes should now once again show for the linked metric + response = client.get("/metrics/default.num_messages/") + assert response.ok + assert response.json()["dimensions"] == [ + {"name": "default.messages.user_id", "path": [], "type": "int"}, + { + "name": "default.us_users.age", + "path": ["default.messages.user_id"], + "type": "int", + }, + { + "name": "default.us_users.country", + "path": ["default.messages.user_id"], + "type": "string", + }, + { + "name": "default.us_users.created_at", + "path": ["default.messages.user_id"], + "type": "timestamp", + }, + { + "name": "default.us_users.full_name", + "path": ["default.messages.user_id"], + "type": "string", + }, + { + "name": "default.us_users.gender", + "path": ["default.messages.user_id"], + "type": "string", + }, + { + "name": "default.us_users.id", + "path": ["default.messages.user_id"], + "type": "int", + }, + { + "name": "default.us_users.post_processing_timestamp", + "path": ["default.messages.user_id"], + "type": "timestamp", + }, + { + "name": "default.us_users.preferred_language", + "path": ["default.messages.user_id"], + "type": "string", + }, + { + "name": "default.us_users.secret_number", + "path": ["default.messages.user_id"], + "type": "float", + }, + ] + # The metric should still be VALID + response = client.get("/nodes/default.num_messages/") + assert response.json()["status"] == NodeStatus.VALID + + def test_restoring_an_already_active_node( + self, + client: TestClient, + ): + """ + Test raising when restoring an already active node + """ + response = client.post("/catalogs/", json={"name": "warehouse"}) + assert response.ok + response = client.post("/namespaces/default/") + assert response.ok + response = client.post( + "/nodes/source/", + json={ + "name": "default.users", + "description": "A user table", + "columns": [ + {"name": "id", "type": "int"}, + {"name": "full_name", "type": "string"}, + {"name": "age", "type": "int"}, + {"name": "country", "type": "string"}, + {"name": "gender", "type": "string"}, + {"name": "preferred_language", "type": "string"}, + {"name": "secret_number", "type": "float"}, + {"name": "created_at", "type": "timestamp"}, + {"name": "post_processing_timestamp", "type": "timestamp"}, + ], + "mode": "published", + "catalog": "warehouse", + "schema_": "db", + "table": "users", + }, + ) + assert response.ok + response = client.post("/nodes/default.users/restore/") + assert not response.ok + assert response.json() == { + "message": "Cannot restore `default.users`, node already active.", + "errors": [], + "warnings": [], + } + + def test_hard_deleting_a_node( + self, + client_with_examples: TestClient, + ): + """ + Test raising when restoring an already active node + """ + # Hard deleting a node causes downstream nodes to become invalid + response = client_with_examples.delete("/nodes/default.repair_orders/hard/") + assert response.ok + assert response.json() == { + "message": "The node `default.repair_orders` has been completely removed.", + "impact": [ + { + "name": "default.repair_order", + "status": "invalid", + "effect": "downstream node is now invalid", + }, + { + "effect": "downstream node is now invalid", + "name": "default.regional_level_agg", + "status": "invalid", + }, + { + "effect": "downstream node is now invalid", + "name": "default.regional_repair_efficiency", + "status": "valid", + }, + { + "name": "default.num_repair_orders", + "status": "invalid", + "effect": "downstream node is now invalid", + }, + { + "name": "default.avg_time_to_dispatch", + "status": "invalid", + "effect": "downstream node is now invalid", + }, + ], + } + + # Hard deleting a dimension creates broken links + response = client_with_examples.delete("/nodes/default.repair_order/hard/") + assert response.ok + assert response.json() == { + "message": "The node `default.repair_order` has been completely removed.", + "impact": [ + { + "name": "default.repair_order_details", + "status": "valid", + "effect": "broken link", + }, + { + "effect": "broken link", + "name": "default.regional_level_agg", + "status": "invalid", + }, + { + "effect": "broken link", + "name": "default.national_level_agg", + "status": "valid", + }, + { + "effect": "broken link", + "name": "default.regional_repair_efficiency", + "status": "invalid", + }, + { + "name": "default.avg_repair_price", + "status": "valid", + "effect": "broken link", + }, + { + "name": "default.total_repair_cost", + "status": "valid", + "effect": "broken link", + }, + { + "name": "default.discounted_orders_rate", + "status": "valid", + "effect": "broken link", + }, + { + "name": "default.total_repair_order_discounts", + "status": "valid", + "effect": "broken link", + }, + { + "name": "default.avg_repair_order_discounts", + "status": "valid", + "effect": "broken link", + }, + ], + } + + # Hard deleting an unlinked dimension has no impact + response = client_with_examples.delete("/nodes/default.municipality_dim/hard/") + assert response.ok + assert response.json() == { + "message": "The node `default.municipality_dim` has been completely removed.", + "impact": [], + } + + # Hard delete a metric + response = client_with_examples.delete( + "/nodes/default.avg_repair_order_discounts/hard/", + ) + assert response.ok + assert response.json() == { + "message": "The node `default.avg_repair_order_discounts` has been completely removed.", + "impact": [], + } + + def test_register_table_without_query_service( + self, + client: TestClient, + ): + """ + Trying to register a table without a query service set up should fail. + """ + response = client.post("/register/table/foo/bar/baz/") + data = response.json() + assert data["message"] == ( + "Registering tables requires that a query " + "service is configured for table columns inference" + ) + assert response.status_code == 500 + + def test_create_source_node_with_query_service( + self, + client_with_query_service: TestClient, + ): + """ + Creating a source node without columns but with a query service set should + result in the source node columns being inferred via the query service. + """ + response = client_with_query_service.post( + "/register/table/public/basic/comments/", + ) + data = response.json() + assert data["name"] == "source.public.basic.comments" + assert data["type"] == "source" + assert data["display_name"] == "source.public.basic.comments" + assert data["version"] == "v1.0" + assert data["status"] == "valid" + assert data["mode"] == "published" + assert data["catalog"]["name"] == "public" + assert data["schema_"] == "basic" + assert data["table"] == "comments" + assert data["columns"] == [ + {"name": "id", "type": "int", "attributes": [], "dimension": None}, + {"name": "user_id", "type": "int", "attributes": [], "dimension": None}, + { + "name": "timestamp", + "type": "timestamp", + "attributes": [], + "dimension": None, + }, + {"name": "text", "type": "string", "attributes": [], "dimension": None}, + ] + assert response.status_code == 201 + + def test_refresh_source_node( + self, + client_with_query_service: TestClient, + ): + """ + Refresh a source node with a query service + """ + response = client_with_query_service.post( + "/nodes/default.repair_orders/refresh/", + ) + data = response.json() + + # Columns have changed, so the new node revision should be bumped to a new + # version with an additional `ratings` column. Existing dimension links remain + new_columns = [ + { + "attributes": [], + "dimension": {"name": "default.repair_order"}, + "name": "repair_order_id", + "type": "int", + }, + { + "attributes": [], + "dimension": None, + "name": "municipality_id", + "type": "string", + }, + {"attributes": [], "dimension": None, "name": "hard_hat_id", "type": "int"}, + { + "attributes": [], + "dimension": None, + "name": "order_date", + "type": "timestamp", + }, + { + "attributes": [], + "dimension": None, + "name": "required_date", + "type": "timestamp", + }, + { + "attributes": [], + "dimension": None, + "name": "dispatched_date", + "type": "timestamp", + }, + { + "attributes": [], + "dimension": None, + "name": "dispatcher_id", + "type": "int", + }, + {"attributes": [], "dimension": None, "name": "rating", "type": "int"}, + ] + assert data["version"] == "v2.0" + assert data["columns"] == new_columns + assert response.status_code == 201 + + response = client_with_query_service.get("/history?node=default.repair_orders") + history = response.json() + assert [ + (activity["activity_type"], activity["entity_type"]) for activity in history + ] == [("create", "node"), ("create", "link"), ("refresh", "node")] + + # Refresh it again, but this time no columns will have changed so + # verify that the node revision stays the same + response = client_with_query_service.post( + "/nodes/default.repair_orders/refresh/", + ) + data_second = response.json() + assert data_second["version"] == "v2.0" + assert data_second["node_revision_id"] == data["node_revision_id"] + assert data_second["columns"] == new_columns + + def test_create_update_source_node( + self, + client_with_examples: TestClient, + ) -> None: + """ + Test creating and updating a source node + """ + basic_source_comments = { + "name": "basic.source.comments", + "description": "A fact table with comments", + "columns": [ + {"name": "id", "type": "int"}, + { + "name": "user_id", + "type": "int", + "dimension": "basic.dimension.users", + }, + {"name": "timestamp", "type": "timestamp"}, + {"name": "text", "type": "string"}, + ], + "mode": "published", + "catalog": "public", + "schema_": "basic", + "table": "comments", + } + + # Trying to create it again should fail + response = client_with_examples.post( + "/nodes/source/", + json=basic_source_comments, + ) + data = response.json() + assert ( + data["message"] + == "A node with name `basic.source.comments` already exists." + ) + assert response.status_code == 409 + + # Update node with a new description should create a new revision + response = client_with_examples.patch( + f"/nodes/{basic_source_comments['name']}/", + json={ + "description": "New description", + "display_name": "Comments facts", + }, + ) + data = response.json() + + assert data["name"] == "basic.source.comments" + assert data["display_name"] == "Comments facts" + assert data["type"] == "source" + assert data["version"] == "v1.1" + assert data["description"] == "New description" + + # Try to update node with no changes + response = client_with_examples.patch( + f"/nodes/{basic_source_comments['name']}/", + json={"description": "New description", "display_name": "Comments facts"}, + ) + new_data = response.json() + assert data == new_data + + # Try to update a node with a table that has different columns + response = client_with_examples.patch( + f"/nodes/{basic_source_comments['name']}/", + json={ + "columns": [ + {"name": "id", "type": "int"}, + { + "name": "user_id", + "type": "int", + "dimension": "basic.dimension.users", + }, + {"name": "timestamp", "type": "timestamp"}, + {"name": "text_v2", "type": "string"}, + ], + }, + ) + data = response.json() + assert data["version"] == "v2.0" + assert data["columns"] == [ + {"name": "id", "type": "int", "attributes": [], "dimension": None}, + {"name": "user_id", "type": "int", "attributes": [], "dimension": None}, + { + "name": "timestamp", + "type": "timestamp", + "attributes": [], + "dimension": None, + }, + {"name": "text_v2", "type": "string", "attributes": [], "dimension": None}, + ] + + def test_update_nonexistent_node( + self, + client: TestClient, + ) -> None: + """ + Test updating a non-existent node. + """ + + response = client.patch( + "/nodes/something/", + json={"description": "new"}, + ) + data = response.json() + assert response.status_code == 404 + assert data["message"] == "A node with name `something` does not exist." + + def test_raise_on_source_node_with_no_catalog( + self, + client: TestClient, + ) -> None: + """ + Test raise on source node with no catalog + """ + response = client.post( + "/nodes/source/", + json={ + "name": "basic.source.comments", + "description": "A fact table with comments", + "columns": [ + {"name": "id", "type": "int"}, + { + "name": "user_id", + "type": "int", + "dimension": "basic.dimension.users", + }, + {"name": "timestamp", "type": "timestamp"}, + {"name": "text", "type": "string"}, + ], + "mode": "published", + }, + ) + assert not response.ok + assert response.json() == { + "detail": [ + { + "loc": ["body", "catalog"], + "msg": "field required", + "type": "value_error.missing", + }, + { + "loc": ["body", "schema_"], + "msg": "field required", + "type": "value_error.missing", + }, + { + "loc": ["body", "table"], + "msg": "field required", + "type": "value_error.missing", + }, + ], + } + + def test_create_invalid_transform_node( + self, + database: Database, # pylint: disable=unused-argument + source_node: Node, # pylint: disable=unused-argument + client: TestClient, + create_invalid_transform_node_payload: Dict[str, Any], + ) -> None: + """ + Test creating an invalid transform node in draft and published modes. + """ + client.post("/namespaces/default/") + response = client.post( + "/nodes/transform/", + json=create_invalid_transform_node_payload, + ) + data = response.json() + assert response.status_code == 400 + assert data["message"].startswith( + "Node definition contains references to nodes that do not exist", + ) + + def test_create_node_with_type_inference_failure( + self, + client_with_examples: TestClient, + ): + """ + Attempting to create a published metric where type inference fails should raise + an appropriate error and fail. + """ + response = client_with_examples.post( + "/nodes/metric/", + json={ + "description": "Average length of employment", + "query": ( + "SELECT avg(NOW() - hire_date + 1) as " + "default_DOT_avg_length_of_employment_plus_one " + "FROM foo.bar.hard_hats" + ), + "mode": "published", + "name": "default.avg_length_of_employment_plus_one", + }, + ) + data = response.json() + assert data == { + "message": ( + "Unable to infer type for some columns on node " + "`default.avg_length_of_employment_plus_one`.\n\n" + "\t* Incompatible types in binary operation NOW() - " + "foo.bar.hard_hats.hire_date + 1. Got left timestamp, rig" + ), + "errors": [ + { + "code": 302, + "message": ( + "Unable to infer type for some columns on node " + "`default.avg_length_of_employment_plus_one`.\n\n" + "\t* Incompatible types in binary operation NOW() - " + "foo.bar.hard_hats.hire_date + 1. Got left timestamp, rig" + ), + "debug": { + "columns": { + "default_DOT_avg_length_of_employment_plus_one": ( + "Incompatible types in binary operation NOW() - " + "foo.bar.hard_hats.hire_date + 1. Got left timestamp, right int." + ), + }, + "errors": [], + }, + "context": "", + }, + ], + "warnings": [], + } + + def test_create_update_transform_node( + self, + database: Database, # pylint: disable=unused-argument + source_node: Node, # pylint: disable=unused-argument + client: TestClient, + create_transform_node_payload: Dict[str, Any], + ) -> None: + """ + Test creating and updating a transform node that references an existing source. + """ + + client.post("/namespaces/default/") + # Create a transform node + response = client.post( + "/nodes/transform/", + json=create_transform_node_payload, + ) + data = response.json() + assert data["name"] == "default.country_agg" + assert data["display_name"] == "Default: Country Agg" + assert data["type"] == "transform" + assert data["description"] == "Distinct users per country" + assert ( + data["query"] + == "SELECT country, COUNT(DISTINCT id) AS num_users FROM basic.source.users" + ) + assert data["status"] == "valid" + assert data["columns"] == [ + {"name": "country", "type": "string", "attributes": [], "dimension": None}, + { + "name": "num_users", + "type": "bigint", + "attributes": [], + "dimension": None, + }, + ] + assert data["parents"] == [{"name": "basic.source.users"}] + + # Update the transform node with two minor changes + response = client.patch( + "/nodes/default.country_agg/", + json={ + "description": "Some new description", + "display_name": "Default: Country Aggregation by User", + }, + ) + data = response.json() + assert data["name"] == "default.country_agg" + assert data["display_name"] == "Default: Country Aggregation by User" + assert data["type"] == "transform" + assert data["version"] == "v1.1" + assert data["description"] == "Some new description" + assert ( + data["query"] + == "SELECT country, COUNT(DISTINCT id) AS num_users FROM basic.source.users" + ) + assert data["status"] == "valid" + + # Try to update with a new query that references a non-existent source + response = client.patch( + "/nodes/default.country_agg/", + json={ + "query": "SELECT country, COUNT(DISTINCT id) AS num_users FROM comments", + }, + ) + data = response.json() + assert data["message"].startswith( + "Node definition contains references to nodes that do not exist", + ) + + # Try to update with a new query that references an existing source + response = client.patch( + "/nodes/default.country_agg/", + json={ + "query": "SELECT country, COUNT(DISTINCT id) AS num_users, " + "COUNT(*) AS num_entries FROM basic.source.users", + }, + ) + data = response.json() + assert data["version"] == "v2.0" + assert ( + data["query"] == "SELECT country, COUNT(DISTINCT id) AS num_users, " + "COUNT(*) AS num_entries FROM basic.source.users" + ) + assert data["columns"] == [ + {"name": "country", "type": "string", "attributes": [], "dimension": None}, + { + "name": "num_users", + "type": "bigint", + "attributes": [], + "dimension": None, + }, + { + "name": "num_entries", + "type": "bigint", + "attributes": [], + "dimension": None, + }, + ] + assert data["status"] == "valid" + + # Verify that asking for revisions for a non-existent transform fails + response = client.get("/nodes/random_transform/revisions/") + data = response.json() + assert data["message"] == "A node with name `random_transform` does not exist." + + # Verify that all historical revisions are available for the node + response = client.get("/nodes/default.country_agg/revisions/") + data = response.json() + assert {rev["version"]: rev["query"] for rev in data} == { + "v1.0": "SELECT country, COUNT(DISTINCT id) AS num_users FROM basic.source.users", + "v1.1": "SELECT country, COUNT(DISTINCT id) AS num_users FROM basic.source.users", + "v2.0": "SELECT country, COUNT(DISTINCT id) AS num_users, COUNT(*) AS num_entries " + "FROM basic.source.users", + } + assert {rev["version"]: rev["columns"] for rev in data} == { + "v1.0": [ + { + "name": "country", + "type": "string", + "attributes": [], + "dimension": None, + }, + { + "name": "num_users", + "type": "bigint", + "attributes": [], + "dimension": None, + }, + ], + "v1.1": [ + { + "name": "country", + "type": "string", + "attributes": [], + "dimension": None, + }, + { + "name": "num_users", + "type": "bigint", + "attributes": [], + "dimension": None, + }, + ], + "v2.0": [ + { + "name": "country", + "type": "string", + "attributes": [], + "dimension": None, + }, + { + "name": "num_users", + "type": "bigint", + "attributes": [], + "dimension": None, + }, + { + "name": "num_entries", + "type": "bigint", + "attributes": [], + "dimension": None, + }, + ], + } + + def test_update_metric_node(self, client_with_examples: TestClient): + """ + Verify that during metric node updates, if the query changes, DJ will automatically + alias the metric column. If this aliased query is the same as the current revision's + query, DJ won't promote the version. + """ + response = client_with_examples.patch( + "/nodes/default.total_repair_cost/", + json={"query": "SELECT sum(price) FROM default.repair_order_details"}, + ) + node_data = response.json() + assert node_data["query"] == ( + "SELECT sum(price) default_DOT_total_repair_cost \n" + " FROM default.repair_order_details\n\n" + ) + response = client_with_examples.get("/nodes/default.total_repair_cost") + assert response.json()["version"] == "v1.0" + + response = client_with_examples.patch( + "/nodes/default.total_repair_cost/", + json={"query": "SELECT count(price) FROM default.repair_order_details"}, + ) + node_data = response.json() + assert node_data["query"] == ( + "SELECT count(price) default_DOT_total_repair_cost \n" + " FROM default.repair_order_details\n\n" + ) + response = client_with_examples.get("/nodes/default.total_repair_cost") + assert response.json()["version"] == "v2.0" + + def test_create_dimension_node_fails( + self, + database: Database, # pylint: disable=unused-argument + source_node: Node, # pylint: disable=unused-argument + client: TestClient, + ): + """ + Test various failure cases for dimension node creation. + """ + client.post("/namespaces/default/") + response = client.post( + "/nodes/dimension/", + json={ + "description": "Country dimension", + "query": "SELECT country, COUNT(1) AS user_cnt " + "FROM basic.source.users GROUP BY country", + "mode": "published", + "name": "countries", + }, + ) + assert ( + response.json()["message"] == "Dimension nodes must define a primary key!" + ) + + response = client.post( + "/nodes/dimension/", + json={ + "description": "Country dimension", + "query": "SELECT country, COUNT(1) AS user_cnt " + "FROM basic.source.users GROUP BY country", + "mode": "published", + "name": "default.countries", + "primary_key": ["country", "id"], + }, + ) + assert response.json()["message"] == ( + "Some columns in the primary key [country,id] were not " + "found in the list of available columns for the node " + "default.countries." + ) + + def test_create_update_dimension_node( + self, + database: Database, # pylint: disable=unused-argument + source_node: Node, # pylint: disable=unused-argument + client: TestClient, + create_dimension_node_payload: Dict[str, Any], + ) -> None: + """ + Test creating and updating a dimension node that references an existing source. + """ + client.post("/namespaces/default/") + response = client.post( + "/nodes/dimension/", + json=create_dimension_node_payload, + ) + data = response.json() + + assert response.status_code == 201 + assert data["name"] == "default.countries" + assert data["display_name"] == "Default: Countries" + assert data["type"] == "dimension" + assert data["version"] == "v1.0" + assert data["description"] == "Country dimension" + assert ( + data["query"] == "SELECT country, COUNT(1) AS user_cnt " + "FROM basic.source.users GROUP BY country" + ) + assert data["columns"] == [ + { + "name": "country", + "type": "string", + "attributes": [ + {"attribute_type": {"namespace": "system", "name": "primary_key"}}, + ], + "dimension": None, + }, + {"name": "user_cnt", "type": "bigint", "attributes": [], "dimension": None}, + ] + + # Test updating the dimension node with a new query + response = client.patch( + "/nodes/default.countries/", + json={"query": "SELECT country FROM basic.source.users GROUP BY country"}, + ) + data = response.json() + # Should result in a major version update due to the query change + assert data["version"] == "v2.0" + + # The columns should have been updated + assert data["columns"] == [ + { + "name": "country", + "type": "string", + "attributes": [ + {"attribute_type": {"namespace": "system", "name": "primary_key"}}, + ], + "dimension": None, + }, + ] + + def test_raise_on_multi_catalog_node(self, client_with_examples: TestClient): + """ + Test raising when trying to select from multiple catalogs + """ + response = client_with_examples.post( + "/nodes/transform/", + json={ + "query": ( + "SELECT payment_id, payment_amount, customer_id, account_type " + "FROM default.revenue r LEFT JOIN basic.source.comments b on r.id = b.id" + ), + "description": "Multicatalog", + "mode": "published", + "name": "default.multicatalog", + }, + ) + assert ( + "Cannot create nodes with multi-catalog dependencies" + in response.json()["message"] + ) + + def test_updating_node_to_invalid_draft( + self, + database: Database, # pylint: disable=unused-argument + source_node: Node, # pylint: disable=unused-argument + client: TestClient, + create_dimension_node_payload: Dict[str, Any], + ) -> None: + """ + Test creating an invalid node in draft mode + """ + client.post("/namespaces/default/") + response = client.post( + "/nodes/dimension/", + json=create_dimension_node_payload, + ) + data = response.json() + + assert response.status_code == 201 + assert data["name"] == "default.countries" + assert data["display_name"] == "Default: Countries" + assert data["type"] == "dimension" + assert data["version"] == "v1.0" + assert data["description"] == "Country dimension" + assert ( + data["query"] == "SELECT country, COUNT(1) AS user_cnt " + "FROM basic.source.users GROUP BY country" + ) + assert data["columns"] == [ + { + "name": "country", + "type": "string", + "attributes": [ + {"attribute_type": {"namespace": "system", "name": "primary_key"}}, + ], + "dimension": None, + }, + {"name": "user_cnt", "type": "bigint", "attributes": [], "dimension": None}, + ] + + response = client.patch( + "/nodes/default.countries/", + json={"mode": "draft"}, + ) + assert response.status_code == 200 + + # Test updating the dimension node with an invalid query + response = client.patch( + "/nodes/default.countries/", + json={"query": "SELECT country FROM missing_parent GROUP BY country"}, + ) + assert response.status_code == 200 + + # Check that node is now a draft with an invalid status + response = client.get("/nodes/default.countries") + assert response.status_code == 200 + data = response.json() + assert data["mode"] == "draft" + assert data["status"] == "invalid" + + def test_upsert_materialization_config( # pylint: disable=too-many-arguments + self, + client_with_query_service: TestClient, + ) -> None: + """ + Test creating & updating materialization config for a node. + """ + # Setting the materialization config for a source node should fail + response = client_with_query_service.post( + "/nodes/basic.source.comments/materialization/", + json={ + "engine": { + "name": "spark", + "version": "2.4.4", + }, + "schedule": "0 * * * *", + "config": {}, + }, + ) + assert response.status_code == 400 + assert ( + response.json()["message"] + == "Cannot set materialization config for source node `basic.source.comments`!" + ) + + # Setting the materialization config for an engine that doesn't exist should fail + response = client_with_query_service.post( + "/nodes/basic.transform.country_agg/materialization/", + json={ + "engine": { + "name": "spark", + "version": "2.4.4", + }, + "config": {}, + "schedule": "0 * * * *", + }, + ) + assert response.status_code == 404 + data = response.json() + assert data["detail"] == "Engine not found: `spark` version `2.4.4`" + + def test_node_with_struct(self, client_with_examples: TestClient): + """ + Test that building a query string with structs yields a correctly formatted struct + reference. + """ + response = client_with_examples.post( + "/nodes/transform/", + json={ + "description": "Regional level agg with structs", + "query": """SELECT + usr.us_region_id, + us.state_name, + CONCAT(us.state_name, '-', usr.us_region_description) AS location_hierarchy, + EXTRACT(YEAR FROM ro.order_date) AS order_year, + EXTRACT(MONTH FROM ro.order_date) AS order_month, + EXTRACT(DAY FROM ro.order_date) AS order_day, + STRUCT( + COUNT(DISTINCT CASE WHEN ro.dispatched_date IS NOT NULL THEN ro.repair_order_id ELSE NULL END) AS completed_repairs, + COUNT(DISTINCT ro.repair_order_id) AS total_repairs_dispatched, + SUM(rd.price * rd.quantity) AS total_amount_in_region, + AVG(rd.price * rd.quantity) AS avg_repair_amount_in_region, + AVG(DATEDIFF(ro.dispatched_date, ro.order_date)) AS avg_dispatch_delay, + COUNT(DISTINCT c.contractor_id) AS unique_contractors + ) AS measures +FROM default.repair_orders ro +JOIN + default.municipality m ON ro.municipality_id = m.municipality_id +JOIN + default.us_states us ON m.state_id = us.state_id +JOIN + default.us_states us ON m.state_id = us.state_id +JOIN + default.us_region usr ON us.state_region = usr.us_region_id +JOIN + default.repair_order_details rd ON ro.repair_order_id = rd.repair_order_id +JOIN + default.repair_type rt ON rd.repair_type_id = rt.repair_type_id +JOIN + default.contractors c ON rt.contractor_id = c.contractor_id +GROUP BY + usr.us_region_id, + EXTRACT(YEAR FROM ro.order_date), + EXTRACT(MONTH FROM ro.order_date), + EXTRACT(DAY FROM ro.order_date)""", + "mode": "published", + "name": "default.regional_level_agg_structs", + "primary_key": [ + "us_region_id", + "state_name", + "order_year", + "order_month", + "order_day", + ], + }, + ) + assert { + "attributes": [], + "dimension": None, + "name": "measures", + "type": "struct", + } in response.json()["columns"] + + client_with_examples.post( + "/nodes/transform/", + json={ + "description": "Total Repair Amounts during the COVID-19 Pandemic", + "name": "default.total_amount_in_region_from_struct_transform", + "query": "SELECT SUM(IF(order_year = 2020, measures.total_amount_in_region, 0)) " + "FROM default.regional_level_agg_structs", + "mode": "published", + }, + ) + response = client_with_examples.get( + "/sql/default.total_amount_in_region_from_struct_transform?filters=" + "&dimensions=default.regional_level_agg_structs.location_hierarchy", + ) + assert compare_query_strings( + response.json()["sql"], + """SELECT SUM( + IF( + default_DOT_regional_level_agg_structs.order_year = 2020, + default_DOT_regional_level_agg_structs.measures.total_amount_in_region, + 0 + ) + ) col0, + default_DOT_regional_level_agg_structs.location_hierarchy +FROM ( + SELECT CONCAT( + default_DOT_us_states.state_name, + '-', + default_DOT_us_region.us_region_description + ) AS location_hierarchy, + struct( + COUNT( + DISTINCT CASE + WHEN default_DOT_repair_orders.dispatched_date IS NOT NULL + THEN default_DOT_repair_orders.repair_order_id + ELSE NULL + END + ) AS completed_repairs, + COUNT( + DISTINCT default_DOT_repair_orders.repair_order_id + ) AS total_repairs_dispatched, + SUM( + default_DOT_repair_order_details.price * default_DOT_repair_order_details.quantity + ) AS total_amount_in_region, + AVG( + default_DOT_repair_order_details.price * default_DOT_repair_order_details.quantity + ) AS avg_repair_amount_in_region, + AVG( + DATEDIFF( + default_DOT_repair_orders.dispatched_date, + default_DOT_repair_orders.order_date + ) + ) AS avg_dispatch_delay, + COUNT(DISTINCT default_DOT_contractors.contractor_id) AS unique_contractors + ) AS measures, + EXTRACT(DAY, default_DOT_repair_orders.order_date) AS order_day, + EXTRACT(MONTH, default_DOT_repair_orders.order_date) AS order_month, + EXTRACT(YEAR, default_DOT_repair_orders.order_date) AS order_year, + default_DOT_us_states.state_name, + default_DOT_us_region.us_region_id + FROM roads.repair_orders AS default_DOT_repair_orders + JOIN roads.municipality AS default_DOT_municipality + ON default_DOT_repair_orders.municipality_id = default_DOT_municipality.municipality_id + JOIN roads.us_states AS default_DOT_us_states + ON default_DOT_municipality.state_id = default_DOT_us_states.state_id + JOIN roads.us_states AS default_DOT_us_states + ON default_DOT_municipality.state_id = default_DOT_us_states.state_id + JOIN roads.us_region AS default_DOT_us_region + ON default_DOT_us_states.state_region = default_DOT_us_region.us_region_id + JOIN roads.repair_order_details AS default_DOT_repair_order_details + ON default_DOT_repair_orders.repair_order_id = + default_DOT_repair_order_details.repair_order_id + JOIN roads.repair_type AS default_DOT_repair_type + ON default_DOT_repair_order_details.repair_type_id = default_DOT_repair_type.repair_type_id + JOIN roads.contractors AS default_DOT_contractors + ON default_DOT_repair_type.contractor_id = default_DOT_contractors.contractor_id + GROUP BY default_DOT_us_region.us_region_id, + EXTRACT(YEAR, default_DOT_repair_orders.order_date), + EXTRACT(MONTH, default_DOT_repair_orders.order_date), + EXTRACT(DAY, default_DOT_repair_orders.order_date) + ) AS default_DOT_regional_level_agg_structs +GROUP BY default_DOT_regional_level_agg_structs.location_hierarchy""", + ) + + def test_node_with_incremental_materialization( + self, + client_with_query_service: TestClient, + ) -> None: + """ + 1. Create a transform node that uses dj_logical_timestamp (i.e., it is + meant to be incrementally materialized). + 2. Create a metric node that references the above transform. + 3. When SQL for the metric is requested without the transform having been materialized, + the request will fail. + """ + client_with_query_service.post( + "/nodes/transform/", + json={ + "description": "Repair orders transform (partitioned)", + "query": """ + SELECT + repair_order_id, + municipality_id, + hard_hat_id, + order_date, + required_date, + dispatched_date, + dispatcher_id, + dj_logical_timestamp('%Y%m%d') as date_partition + FROM default.repair_orders + WHERE date_format(order_date, 'yyyyMMdd') = dj_logical_timestamp('%Y%m%d') + """, + "mode": "published", + "name": "default.repair_orders_partitioned", + "primary_key": ["repair_order_id"], + }, + ) + client_with_query_service.post( + "/nodes/default.repair_orders_partitioned/columns/hard_hat_id/" + "?dimension=default.hard_hat&dimension_column=hard_hat_id", + ) + + client_with_query_service.post( + "/nodes/metric/", + json={ + "description": "Number of repair orders", + "query": "SELECT count(repair_order_id) FROM default.repair_orders_partitioned", + "mode": "published", + "name": "default.num_repair_orders_partitioned", + }, + ) + response = client_with_query_service.get( + "/sql?metrics=default.num_repair_orders_partitioned" + "&dimensions=default.hard_hat.last_name", + ) + format_regex = r"\${(?P[^}]+)}" + + result_sql = response.json()["sql"] + + match = re.search(format_regex, result_sql) + assert match and match.group("capture") == "dj_logical_timestamp" + query = re.sub(format_regex, "FORMATTED", result_sql) + compare_query_strings( + query, + """WITH +m0_default_DOT_num_repair_orders_partitioned AS (SELECT default_DOT_hard_hat.last_name, + count(default_DOT_repair_orders_partitioned.repair_order_id) + default_DOT_num_repair_orders_partitioned + FROM (SELECT FORMATTED AS date_partition, + default_DOT_repair_orders.dispatched_date, + default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.order_date, + default_DOT_repair_orders.repair_order_id, + default_DOT_repair_orders.required_date + FROM roads.repair_orders AS default_DOT_repair_orders + WHERE date_format(default_DOT_repair_orders.order_date, 'yyyyMMdd') = FORMATTED) + AS default_DOT_repair_orders_partitioned LEFT OUTER JOIN + (SELECT default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.last_name, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats) + AS default_DOT_hard_hat ON + default_DOT_repair_orders_partitioned.hard_hat_id = default_DOT_hard_hat.hard_hat_id + GROUP BY default_DOT_hard_hat.last_name +) + +SELECT m0_default_DOT_num_repair_orders_partitioned.default_DOT_num_repair_orders_partitioned, + m0_default_DOT_num_repair_orders_partitioned.last_name + FROM m0_default_DOT_num_repair_orders_partitioned""", + ) + + client_with_query_service.post( + "/engines/", + json={ + "name": "spark", + "version": "2.4.4", + "dialect": "spark", + }, + ) + + # Setting the materialization config should succeed + response = client_with_query_service.post( + "/nodes/default.repair_orders_partitioned/materialization/", + json={ + "engine": { + "name": "spark", + "version": "2.4.4", + }, + "config": { + "partitions": [], + }, + "schedule": "0 * * * *", + }, + ) + data = response.json() + assert ( + data["message"] == "Successfully updated materialization config named " + "`default` for node `default.repair_orders_partitioned`" + ) + + response = client_with_query_service.get( + "/nodes/default.repair_orders_partitioned", + ) + result_sql = response.json()["materializations"][0]["config"]["query"] + match = re.search(format_regex, result_sql) + assert match and match.group("capture") == "dj_logical_timestamp" + query = re.sub(format_regex, "FORMATTED", result_sql) + compare_query_strings( + query, + "SELECT FORMATTED AS date_partition,\n\t" + "default_DOT_repair_orders.dispatched_date,\n\t" + "default_DOT_repair_orders.dispatcher_id,\n\t" + "default_DOT_repair_orders.hard_hat_id,\n\t" + "default_DOT_repair_orders.municipality_id,\n\t" + "default_DOT_repair_orders.order_date,\n\t" + "default_DOT_repair_orders.repair_order_id,\n\t" + "default_DOT_repair_orders.required_date \n" + " FROM roads.repair_orders AS " + "default_DOT_repair_orders \n" + " WHERE " + "date_format(default_DOT_repair_orders.order_date, " + "'yyyyMMdd') = FORMATTED\n\n", + ) + + def test_update_node_query_with_materializations( + self, + client_with_query_service: TestClient, + ): + """ + Testing updating a node's query when the node already has materializations. The node's + materializations should be updated based on the new query and rescheduled. + """ + client_with_query_service.post( + "/engines/", + json={ + "name": "spark", + "version": "2.4.4", + "dialect": "spark", + }, + ) + + client_with_query_service.post( + "/nodes/basic.transform.country_agg/materialization/", + json={ + "engine": { + "name": "spark", + "version": "2.4.4", + }, + "config": { + "partitions": [ + { + "name": "country", + "values": ["DE", "MY"], + "type_": "categorical", + }, + ], + }, + "schedule": "0 * * * *", + }, + ) + client_with_query_service.patch( + "/nodes/basic.transform.country_agg/", + json={ + "query": ( + "SELECT country, COUNT(DISTINCT id) AS num_users, " + "COUNT(DISTINCT preferred_language) AS languages " + "FROM basic.source.users GROUP BY 1" + ), + }, + ) + response = client_with_query_service.get("/nodes/basic.transform.country_agg/") + node_output = response.json() + assert node_output["materializations"] == [ + { + "name": "country_3491792861", + "engine": { + "name": "spark", + "version": "2.4.4", + "uri": None, + "dialect": "spark", + }, + "config": { + "partitions": [ + { + "name": "country", + "values": ["DE", "MY"], + "range": None, + "expression": None, + "type_": "categorical", + }, + ], + "spark": {}, + "query": "SELECT basic_DOT_source_DOT_users.country,\n\t" + "COUNT( DISTINCT basic_DOT_source_DOT_users.preferred_language) " + "AS languages,\n\tCOUNT( DISTINCT basic_DOT_source_DOT_users.id) " + "AS num_users \n FROM basic.dim_users AS basic_DOT_source_DOT_users " + "\n WHERE basic_DOT_source_DOT_users.country IN ('DE', 'MY') \n " + "GROUP BY 1\n\n", + "upstream_tables": ["public.basic.dim_users"], + }, + "schedule": "0 * * * *", + "job": "SparkSqlMaterializationJob", + }, + ] + + def test_add_materialization_success(self, client_with_query_service: TestClient): + """ + Verifies success cases of adding materialization config. + """ + # Create the engine and check the existing transform node + client_with_query_service.post( + "/engines/", + json={ + "name": "spark", + "version": "2.4.4", + "dialect": "spark", + }, + ) + + response = client_with_query_service.get("/nodes/basic.transform.country_agg/") + old_node_data = response.json() + assert old_node_data["version"] == "v1.0" + assert old_node_data["materializations"] == [] + + # Setting the materialization config should succeed + response = client_with_query_service.post( + "/nodes/basic.transform.country_agg/materialization/", + json={ + "engine": { + "name": "spark", + "version": "2.4.4", + }, + "config": { + "partitions": [ + { + "name": "country", + "values": ["DE", "MY"], + "type_": "categorical", + }, + ], + }, + "schedule": "0 * * * *", + }, + ) + data = response.json() + assert ( + data["message"] == "Successfully updated materialization config named " + "`country_3491792861` for node `basic.transform.country_agg`" + ) + + # Check history of the node with materialization + response = client_with_query_service.get( + "/history?node=basic.transform.country_agg", + ) + history = response.json() + assert [ + (activity["activity_type"], activity["entity_type"]) for activity in history + ] == [("create", "node"), ("create", "materialization")] + + # Setting it again should inform that it already exists + response = client_with_query_service.post( + "/nodes/basic.transform.country_agg/materialization/", + json={ + "engine": { + "name": "spark", + "version": "2.4.4", + }, + "config": { + "partitions": [ + { + "name": "country", + "values": ["DE", "MY"], + "type_": "categorical", + }, + ], + }, + "schedule": "0 * * * *", + }, + ) + assert response.json() == { + "info": { + "output_tables": ["common.a", "common.b"], + "urls": ["http://fake.url/job"], + }, + "message": "The same materialization config with name " + "`country_3491792861` already exists for node " + "`basic.transform.country_agg` so no update was performed.", + } + + response = client_with_query_service.delete( + "/nodes/basic.transform.country_agg/materializations/" + "?materialization_name=country_3491792861", + ) + assert response.json() == { + "message": "The materialization named `country_3491792861` on node " + "`basic.transform.country_agg` has been successfully deactivated", + } + + # Setting it again should inform that it already exists but was reactivated + response = client_with_query_service.post( + "/nodes/basic.transform.country_agg/materialization/", + json={ + "engine": { + "name": "spark", + "version": "2.4.4", + }, + "config": { + "partitions": [ + { + "name": "country", + "values": ["DE", "MY"], + "type_": "categorical", + }, + ], + }, + "schedule": "0 * * * *", + }, + ) + assert response.json()["message"] == ( + "The same materialization config with name `country_3491792861` already " + "exists for node `basic.transform.country_agg` but was deactivated. It has " + "now been restored." + ) + response = client_with_query_service.get( + "/history?node=basic.transform.country_agg", + ) + assert [ + ( + activity["activity_type"], + activity["entity_type"], + activity["entity_name"], + ) + for activity in response.json() + ] == [ + ("create", "node", "basic.transform.country_agg"), + ("create", "materialization", "country_3491792861"), + ("delete", "materialization", "country_3491792861"), + ("restore", "materialization", "country_3491792861"), + ] + + # Setting the materialization config without partitions should succeed + response = client_with_query_service.post( + "/nodes/basic.transform.country_agg/materialization/", + json={ + "engine": { + "name": "spark", + "version": "2.4.4", + }, + "config": { + "partitions": [], + }, + "schedule": "0 * * * *", + }, + ) + data = response.json() + assert ( + data["message"] == "Successfully updated materialization config named " + "`default` for node `basic.transform.country_agg`" + ) + + # Reading the node should yield the materialization config + response = client_with_query_service.get("/nodes/basic.transform.country_agg/") + data = response.json() + assert data["version"] == "v1.0" + materialization_compare( + data["materializations"], + [ + { + "name": "country_3491792861", + "engine": { + "name": "spark", + "version": "2.4.4", + "uri": None, + "dialect": "spark", + }, + "config": { + "query": "SELECT basic_DOT_source_DOT_users.country,\n\tCOUNT( " + "DISTINCT basic_DOT_source_DOT_users.id) AS num_users \n " + "FROM basic.dim_users AS basic_DOT_source_DOT_users \n WHERE" + " basic_DOT_source_DOT_users.country IN ('DE', 'MY') \n " + "GROUP BY 1\n", + "partitions": [ + { + "name": "country", + "values": ["DE", "MY"], + "range": None, + "type_": "categorical", + "expression": None, + }, + ], + "spark": {}, + "upstream_tables": ["public.basic.dim_users"], + }, + "schedule": "0 * * * *", + "job": "SparkSqlMaterializationJob", + }, + { + "config": { + "partitions": [], + "query": "SELECT basic_DOT_source_DOT_users.country,\n" + "\tCOUNT( DISTINCT basic_DOT_source_DOT_users.id) AS " + "num_users \n" + " FROM basic.dim_users AS basic_DOT_source_DOT_users \n" + " GROUP BY 1\n", + "spark": {}, + "upstream_tables": ["public.basic.dim_users"], + }, + "engine": { + "dialect": "spark", + "name": "spark", + "uri": None, + "version": "2.4.4", + }, + "job": "SparkSqlMaterializationJob", + "name": "default", + "schedule": "0 * * * *", + }, + ], + ) + + # Setting the materialization config with a temporal partition should succeed + response = client_with_query_service.post( + "/nodes/default.hard_hat/materialization/", + json={ + "engine": { + "name": "spark", + "version": "2.4.4", + }, + "config": { + "partitions": [ + { + "name": "country", + "values": ["DE", "MY"], + "type_": "categorical", + }, + { + "name": "birth_date", + "range": (20010101, 20020101), + "type_": "temporal", + }, + { + "name": "contractor_id", + "range": (1, 10), + "type_": "categorical", + }, + ], + }, + "schedule": "0 * * * *", + }, + ) + data = response.json() + assert ( + data["message"] == "Successfully updated materialization config named " + "`country_birth_date_contractor_id_379232101` for node `default.hard_hat`" + ) + + # Check that the temporal partition is appended onto the list of partitions in the + # materialization config but is not included directly in the materialization query + response = client_with_query_service.get("/nodes/default.hard_hat/") + data = response.json() + assert data["version"] == "v1.0" + materialization_compare( + data["materializations"], + [ + { + "name": "country_birth_date_contractor_id_379232101", + "engine": { + "name": "spark", + "version": "2.4.4", + "uri": None, + "dialect": "spark", + }, + "config": { + "query": "SELECT default_DOT_hard_hats.address,\n\tdefault_DOT_hard_hats." + "birth_date,\n\tdefault_DOT_hard_hats.city,\n\tdefault_DOT_hard_hats." + "contractor_id,\n\tdefault_DOT_hard_hats.country,\n\tdefault_DOT_hard" + "_hats.first_name,\n\tdefault_DOT_hard_hats.hard_hat_id,\n\tdefault_D" + "OT_hard_hats.hire_date,\n\tdefault_DOT_hard_hats.last_name,\n\tdefau" + "lt_DOT_hard_hats.manager,\n\tdefault_DOT_hard_hats.postal_code,\n\t" + "default_DOT_hard_hats.state,\n\tdefault_DOT_hard_hats.title \n FROM" + " roads.hard_hats AS default_DOT_hard_hats \n WHERE default_DOT_har" + "d_hats.country IN ('DE', 'MY') AND default_DOT_hard_hats.contractor" + "_id BETWEEN 1 AND 10\n", + "partitions": [ + { + "name": "country", + "values": ["DE", "MY"], + "range": None, + "type_": "categorical", + "expression": None, + }, + { + "name": "birth_date", + "values": None, + "range": [20010101, 20020101], + "type_": "temporal", + "expression": None, + }, + { + "name": "contractor_id", + "values": None, + "range": [1, 10], + "type_": "categorical", + "expression": None, + }, + ], + "spark": {}, + "upstream_tables": ["default.roads.hard_hats"], + }, + "schedule": "0 * * * *", + "job": "SparkSqlMaterializationJob", + }, + ], + ) + + # Check listing materializations of the node + response = client_with_query_service.get( + "/nodes/default.hard_hat/materializations/", + ) + materialization_compare( + response.json(), + [ + { + "config": { + "partitions": [ + { + "expression": None, + "name": "country", + "range": None, + "type_": "categorical", + "values": ["DE", "MY"], + }, + { + "expression": None, + "name": "birth_date", + "range": [20010101, 20020101], + "type_": "temporal", + "values": None, + }, + { + "expression": None, + "name": "contractor_id", + "range": [1, 10], + "type_": "categorical", + "values": None, + }, + ], + "query": "SELECT default_DOT_hard_hats.address,\n" + "\tdefault_DOT_hard_hats.birth_date,\n" + "\tdefault_DOT_hard_hats.city,\n" + "\tdefault_DOT_hard_hats.contractor_id,\n" + "\tdefault_DOT_hard_hats.country,\n" + "\tdefault_DOT_hard_hats.first_name,\n" + "\tdefault_DOT_hard_hats.hard_hat_id,\n" + "\tdefault_DOT_hard_hats.hire_date,\n" + "\tdefault_DOT_hard_hats.last_name,\n" + "\tdefault_DOT_hard_hats.manager,\n" + "\tdefault_DOT_hard_hats.postal_code,\n" + "\tdefault_DOT_hard_hats.state,\n" + "\tdefault_DOT_hard_hats.title \n" + " FROM roads.hard_hats AS default_DOT_hard_hats \n" + " WHERE default_DOT_hard_hats.country IN ('DE', 'MY') " + "AND default_DOT_hard_hats.contractor_id BETWEEN 1 AND " + "10\n", + "spark": {}, + "upstream_tables": ["default.roads.hard_hats"], + }, + "engine": { + "dialect": "spark", + "name": "spark", + "uri": None, + "version": "2.4.4", + }, + "job": "SparkSqlMaterializationJob", + "name": "country_birth_date_contractor_id_379232101", + "output_tables": ["common.a", "common.b"], + "schedule": "0 * * * *", + "urls": ["http://fake.url/job"], + }, + ], + ) + + +class TestNodeColumnsAttributes: + """ + Test ``POST /nodes/{name}/attributes/``. + """ + + @pytest.fixture + def create_source_node_payload(self) -> Dict[str, Any]: + """ + Payload for creating a source node. + """ + + return { + "name": "comments", + "description": "A fact table with comments", + "type": "source", + "columns": [ + {"name": "id", "type": "int"}, + { + "name": "user_id", + "type": "int", + "dimension": "basic.dimension.users", + }, + {"name": "event_timestamp", "type": "timestamp"}, + {"name": "post_processing_timestamp", "type": "timestamp"}, + {"name": "text", "type": "string"}, + ], + "mode": "published", + } + + @pytest.fixture + def database(self, session: Session) -> Database: + """ + A database fixture. + """ + + database = Database(name="postgres", URI="postgres://") + session.add(database) + session.commit() + return database + + @pytest.fixture + def source_node(self, session: Session, database: Database) -> Node: + """ + A source node fixture. + """ + + table = Table( + database=database, + table="A", + columns=[ + Column(name="ds", type=StringType()), + Column(name="user_id", type=IntegerType()), + ], + ) + node = Node( + name="basic.source.users", + type=NodeType.SOURCE, + current_version="1", + ) + node_revision = NodeRevision( + node=node, + name=node.name, + type=node.type, + version="1", + tables=[table], + columns=[ + Column(name="id", type=IntegerType()), + Column(name="created_at", type=TimestampType()), + Column(name="full_name", type=StringType()), + Column(name="age", type=IntegerType()), + Column(name="country", type=StringType()), + Column(name="gender", type=StringType()), + Column(name="preferred_language", type=StringType()), + ], + ) + session.add(node_revision) + session.commit() + return node + + def test_set_columns_attributes( + self, + client_with_examples: TestClient, + ): + """ + Validate that setting column attributes on the node works. + """ + response = client_with_examples.post( + "/nodes/basic.source.comments/attributes/", + json=[ + { + "attribute_type_namespace": "system", + "attribute_type_name": "primary_key", + "column_name": "id", + }, + ], + ) + data = response.json() + assert data == [ + { + "name": "id", + "type": "int", + "attributes": [ + {"attribute_type": {"name": "primary_key", "namespace": "system"}}, + ], + "dimension": None, + }, + ] + + # Set columns attributes + response = client_with_examples.post( + "/nodes/basic.dimension.users/attributes/", + json=[ + { + "attribute_type_namespace": "system", + "attribute_type_name": "primary_key", + "column_name": "id", + }, + { + "attribute_type_name": "effective_time", + "column_name": "created_at", + }, + ], + ) + data = response.json() + assert data == [ + { + "name": "id", + "type": "int", + "attributes": [ + {"attribute_type": {"name": "primary_key", "namespace": "system"}}, + ], + "dimension": None, + }, + { + "name": "created_at", + "type": "timestamp", + "attributes": [ + { + "attribute_type": { + "name": "effective_time", + "namespace": "system", + }, + }, + ], + "dimension": None, + }, + ] + + def test_set_columns_attributes_failed(self, client_with_examples: TestClient): + """ + Test setting column attributes with different failure modes. + """ + response = client_with_examples.post( + "/nodes/basic.source.comments/attributes/", + json=[ + { + "attribute_type_name": "effective_time", + "column_name": "event_timestamp", + }, + ], + ) + data = response.json() + assert response.status_code == 500 + assert ( + data["message"] + == "Attribute type `system.effective_time` not allowed on node type `source`!" + ) + + response = client_with_examples.get( + "/nodes/basic.source.comments/", + ) + + response = client_with_examples.post( + "/nodes/basic.source.comments/attributes/", + json=[ + { + "attribute_type_name": "primary_key", + "column_name": "nonexistent_col", + }, + ], + ) + assert response.status_code == 404 + data = response.json() + assert data == { + "message": "Column `nonexistent_col` does not exist on node `basic.source.comments`!", + "errors": [], + "warnings": [], + } + + response = client_with_examples.post( + "/nodes/basic.source.comments/attributes/", + json=[ + { + "attribute_type_name": "nonexistent_attribute", + "column_name": "id", + }, + ], + ) + assert response.status_code == 404 + data = response.json() + assert data == { + "message": "Attribute type `system.nonexistent_attribute` does not exist!", + "errors": [], + "warnings": [], + } + + response = client_with_examples.post( + "/nodes/basic.source.comments/attributes/", + json=[ + { + "attribute_type_name": "primary_key", + "column_name": "user_id", + }, + ], + ) + assert response.status_code == 201 + data = response.json() + assert data == [ + { + "name": "user_id", + "type": "int", + "attributes": [ + {"attribute_type": {"name": "primary_key", "namespace": "system"}}, + ], + "dimension": {"name": "basic.dimension.users"}, + }, + ] + + response = client_with_examples.post( + "/nodes/basic.source.comments/attributes/", + json=[ + { + "attribute_type_name": "event_time", + "column_name": "event_timestamp", + }, + { + "attribute_type_name": "event_time", + "column_name": "post_processing_timestamp", + }, + ], + ) + data = response.json() + assert data == { + "message": "The column attribute `event_time` is scoped to be unique to the " + "`['node', 'column_type']` level, but there is more than one column" + " tagged with it: `event_timestamp, post_processing_timestamp`", + "errors": [], + "warnings": [], + } + + response = client_with_examples.get("/nodes/basic.source.comments/") + data = response.json() + assert data["columns"] == [ + { + "name": "id", + "type": "int", + "attributes": [], + "dimension": None, + }, + { + "name": "user_id", + "type": "int", + "attributes": [ + {"attribute_type": {"namespace": "system", "name": "primary_key"}}, + ], + "dimension": {"name": "basic.dimension.users"}, + }, + { + "name": "timestamp", + "type": "timestamp", + "attributes": [], + "dimension": None, + }, + {"name": "text", "type": "string", "attributes": [], "dimension": None}, + { + "name": "event_timestamp", + "type": "timestamp", + "attributes": [], + "dimension": None, + }, + { + "name": "created_at", + "type": "timestamp", + "attributes": [], + "dimension": None, + }, + { + "name": "post_processing_timestamp", + "type": "timestamp", + "attributes": [], + "dimension": None, + }, + ] + + +class TestValidateNodes: # pylint: disable=too-many-public-methods + """ + Test ``POST /nodes/validate/``. + """ + + def test_validating_a_valid_node(self, client_with_examples: TestClient) -> None: + """ + Test validating a valid node + """ + response = client_with_examples.post( + "/nodes/validate/", + json={ + "name": "foo", + "description": "This is my foo transform node!", + "query": "SELECT payment_id FROM default.large_revenue_payments_only", + "type": "transform", + }, + ) + data = response.json() + + assert response.status_code == 200 + assert len(data) == 6 + assert data["columns"] == [ + { + "dimension_column": None, + "dimension_id": None, + "id": None, + "name": "payment_id", + "type": "int", + }, + ] + assert data["status"] == "valid" + assert data["node_revision"]["status"] == "valid" + assert data["dependencies"][0]["name"] == "default.large_revenue_payments_only" + assert data["message"] == "Node `foo` is valid." + assert data["node_revision"]["id"] is None + assert data["node_revision"]["mode"] == "published" + assert data["node_revision"]["name"] == "foo" + assert ( + data["node_revision"]["query"] + == "SELECT payment_id FROM default.large_revenue_payments_only" + ) + + def test_validating_an_invalid_node(self, client: TestClient) -> None: + """ + Test validating an invalid node + """ + + response = client.post( + "/nodes/validate/", + json={ + "name": "foo", + "description": "This is my foo transform node!", + "query": "SELECT bar FROM large_revenue_payments_only", + "type": "transform", + }, + ) + data = response.json() + assert data["message"] == "Node `foo` is invalid." + assert [ + e + for e in data["errors"] + if e + == { + "code": 301, + "message": "Node definition contains references to nodes that do not exist", + "debug": {"missing_parents": ["large_revenue_payments_only"]}, + "context": "", + } + ] + + def test_validating_invalid_sql(self, client: TestClient) -> None: + """ + Test validating an invalid node with invalid SQL + """ + + response = client.post( + "/nodes/validate/", + json={ + "name": "foo", + "description": "This is my foo transform node!", + "query": "SUPER invalid SQL query", + "type": "transform", + }, + ) + data = response.json() + + assert response.status_code == 422 + assert data["message"] == "Node `foo` is invalid." + assert data["status"] == "invalid" + assert data["errors"] == [ + { + "code": 201, + "message": ( + "('Parse error 1:0:', \"mismatched input 'SUPER' expecting " + "{'(', 'ADD', 'ALTER', 'ANALYZE', 'CACHE', 'CLEAR', 'COMMENT', " + "'COMMIT', 'CREATE', 'DELETE', 'DESC', 'DESCRIBE', 'DFS', 'DROP', " + "'EXPLAIN', 'EXPORT', 'FROM', 'GRANT', 'IMPORT', 'INSERT', " + "'LIST', 'LOAD', 'LOCK', 'MAP', 'MERGE', 'MSCK', 'REDUCE', " + "'REFRESH', 'REPAIR', 'REPLACE', 'RESET', 'REVOKE', 'ROLLBACK', " + "'SELECT', 'SET', 'SHOW', 'START', 'TABLE', 'TRUNCATE', 'UNCACHE', " + "'UNLOCK', 'UPDATE', 'USE', 'VALUES', 'WITH'}\")" + ), + "debug": None, + "context": "", + }, + ] + + def test_validating_with_missing_parents(self, client: TestClient) -> None: + """ + Test validating a node with a query that has missing parents + """ + + response = client.post( + "/nodes/validate/", + json={ + "name": "foo", + "description": "This is my foo transform node!", + "query": "SELECT 1 FROM node_that_does_not_exist", + "type": "transform", + }, + ) + data = response.json() + + assert response.status_code == 422 + assert data == { + "message": "Node `foo` is invalid.", + "status": "invalid", + "node_revision": { + "name": "foo", + "display_name": None, + "type": "transform", + "description": "This is my foo transform node!", + "query": "SELECT 1 FROM node_that_does_not_exist", + "mode": "published", + "id": None, + "version": "v0.1", + "node_id": None, + "catalog_id": None, + "schema_": None, + "table": None, + "status": "valid", + "updated_at": mock.ANY, + }, + "dependencies": [], + "columns": [ + { + "id": None, + "name": "col0", + "type": "int", + "dimension_id": None, + "dimension_column": None, + }, + ], + "errors": [ + { + "code": 301, + "message": "Node definition contains references to nodes that do not exist", + "debug": {"missing_parents": ["node_that_does_not_exist"]}, + "context": "", + }, + ], + } + + def test_allowing_missing_parents_for_draft_nodes(self, client: TestClient) -> None: + """ + Test validating a draft node that's allowed to have missing parents + """ + + response = client.post( + "/nodes/validate/", + json={ + "name": "foo", + "description": "This is my foo transform node!", + "query": "SELECT 1 FROM node_that_does_not_exist", + "type": "transform", + "mode": "draft", + }, + ) + data = response.json() + + assert response.status_code == 422 + assert data["message"] == "Node `foo` is invalid." + assert data["status"] == "invalid" + assert data["node_revision"]["mode"] == "draft" + assert data["node_revision"]["status"] == "invalid" + assert data["columns"] == [ + { + "id": None, + "name": "col0", + "type": "int", + "dimension_id": None, + "dimension_column": None, + }, + ] + + def test_raise_when_trying_to_validate_a_source_node( + self, + client: TestClient, + ) -> None: + """ + Test validating a source node which is not possible + """ + + response = client.post( + "/nodes/validate/", + json={ + "name": "foo", + "description": "This is my foo source node!", + "type": "source", + "columns": [ + {"name": "payment_id", "type": "int"}, + {"name": "payment_amount", "type": "float"}, + {"name": "customer_id", "type": "int"}, + {"name": "account_type", "type": "int"}, + ], + "tables": [ + { + "database_id": 1, + "catalog": "test", + "schema": "accounting", + "table": "revenue", + }, + ], + }, + ) + data = response.json() + + assert response.status_code == 500 + assert data == { + "message": "Source nodes cannot be validated", + "errors": [], + "warnings": [], + } + + def test_adding_dimensions_to_node_columns(self, client_with_examples: TestClient): + """ + Test linking dimensions to node columns + """ + # Attach the payment_type dimension to the payment_type column on the revenue node + response = client_with_examples.post( + "/nodes/default.revenue/columns/payment_type/?dimension=default.payment_type", + ) + data = response.json() + assert data == { + "message": ( + "Dimension node default.payment_type has been successfully " + "linked to column payment_type on node default.revenue" + ), + } + response = client_with_examples.get("/nodes/default.revenue") + data = response.json() + assert [ + col["dimension"]["name"] for col in data["columns"] if col["dimension"] + ] == ["default.payment_type"] + + # Check that after deleting the dimension link, none of the columns have links + response = client_with_examples.delete( + "/nodes/default.revenue/columns/payment_type/?dimension=default.payment_type", + ) + data = response.json() + assert data == { + "message": ( + "The dimension link on the node default.revenue's payment_type to " + "default.payment_type has been successfully removed." + ), + } + response = client_with_examples.get("/nodes/default.revenue") + data = response.json() + assert all(col["dimension"] is None for col in data["columns"]) + response = client_with_examples.get("/history?node=default.revenue") + assert [ + (activity["activity_type"], activity["entity_type"]) + for activity in response.json() + ] == [("create", "node"), ("create", "link"), ("delete", "link")] + + # Removing the dimension link again will result in no change + response = client_with_examples.delete( + "/nodes/default.revenue/columns/payment_type/?dimension=default.payment_type", + ) + data = response.json() + assert response.status_code == 304 + assert data == { + "message": "No change was made to payment_type on node default.revenue as the" + " specified dimension link to default.payment_type on None was not found.", + } + # Check history again, no change + response = client_with_examples.get("/history?node=default.revenue") + assert [ + (activity["activity_type"], activity["entity_type"]) + for activity in response.json() + ] == [("create", "node"), ("create", "link"), ("delete", "link")] + + # Check that the proper error is raised when the column doesn't exist + response = client_with_examples.post( + "/nodes/default.revenue/columns/non_existent_column/?dimension=default.payment_type", + ) + assert response.status_code == 404 + data = response.json() + assert data["message"] == ( + "Column non_existent_column does not exist on node default.revenue" + ) + + # Add a dimension including a specific dimension column name + response = client_with_examples.post( + "/nodes/default.revenue/columns/payment_type/" + "?dimension=default.payment_type" + "&dimension_column=payment_type_name", + ) + assert response.status_code == 422 + data = response.json() + assert data["message"] == ( + "The column payment_type has type int and is being linked " + "to the dimension default.payment_type via the dimension column " + "payment_type_name, which has type string. These column " + "types are incompatible and the dimension cannot be linked" + ) + + response = client_with_examples.post( + "/nodes/default.revenue/columns/payment_type/?dimension=basic.dimension.users", + ) + data = response.json() + assert data["message"] == ( + "Cannot add dimension to column, because catalogs do not match: default, public" + ) + + def test_update_node_with_dimension_links(self, client_with_examples: TestClient): + """ + When a node is updated with a new query, the original dimension links and attributes + on its columns should be preserved where possible (that is, where the new and old + columns have the same names). + """ + client_with_examples.patch( + "/nodes/default.hard_hat/", + json={ + "query": """ + SELECT + hard_hat_id, + title, + state + FROM default.hard_hats + """, + }, + ) + response = client_with_examples.get("/history?node=default.hard_hat") + history = response.json() + assert [ + (activity["activity_type"], activity["entity_type"]) for activity in history + ] == [ + ("create", "node"), + ("set_attribute", "column_attribute"), + ("create", "link"), + ("update", "node"), + ] + + response = client_with_examples.get("/nodes/default.hard_hat").json() + assert response["columns"] == [ + { + "name": "hard_hat_id", + "type": "int", + "attributes": [ + {"attribute_type": {"name": "primary_key", "namespace": "system"}}, + ], + "dimension": None, + }, + {"name": "title", "type": "string", "attributes": [], "dimension": None}, + { + "name": "state", + "type": "string", + "attributes": [], + "dimension": {"name": "default.us_state"}, + }, + ] + + # Check history of the node with column attribute set + response = client_with_examples.get( + "/history?node=default.hard_hat", + ) + history = response.json() + assert [ + (activity["activity_type"], activity["entity_type"]) for activity in history + ] == [ + ("create", "node"), + ("set_attribute", "column_attribute"), + ("create", "link"), + ("update", "node"), + ] + + def test_update_dimension_remove_pk_column(self, client_with_examples: TestClient): + """ + When a dimension node is updated with a new query that removes the original primary key + column, either a new primary key must be set or the node will be set to invalid. + """ + response = client_with_examples.patch( + "/nodes/default.hard_hat/", + json={ + "query": """ + SELECT + title, + state + FROM default.hard_hats + """, + # "primary_key": ["title"], + }, + ) + assert response.json()["status"] == "invalid" + response = client_with_examples.patch( + "/nodes/default.hard_hat/", + json={ + "query": """ + SELECT + title, + state + FROM default.hard_hats + """, + "primary_key": ["title"], + }, + ) + assert response.json()["status"] == "valid" + + def test_node_downstreams(self, client_with_examples: TestClient): + """ + Test getting downstream nodes of different node types. + """ + response = client_with_examples.get( + "/nodes/default.event_source/downstream/?node_type=metric", + ) + data = response.json() + assert {node["name"] for node in data} == { + "default.long_events_distinct_countries", + "default.device_ids_count", + } + + response = client_with_examples.get( + "/nodes/default.event_source/downstream/?node_type=transform", + ) + data = response.json() + assert {node["name"] for node in data} == {"default.long_events"} + + response = client_with_examples.get( + "/nodes/default.event_source/downstream/?node_type=dimension", + ) + data = response.json() + assert {node["name"] for node in data} == {"default.country_dim"} + + response = client_with_examples.get("/nodes/default.event_source/downstream/") + data = response.json() + assert {node["name"] for node in data} == { + "default.long_events_distinct_countries", + "default.device_ids_count", + "default.long_events", + "default.country_dim", + } + + response = client_with_examples.get( + "/nodes/default.device_ids_count/downstream/", + ) + data = response.json() + assert data == [] + + response = client_with_examples.get("/nodes/default.long_events/downstream/") + data = response.json() + assert {node["name"] for node in data} == { + "default.long_events_distinct_countries", + } + + def test_node_upstreams(self, client_with_examples: TestClient): + """ + Test getting upstream nodes of different node types. + """ + response = client_with_examples.get( + "/nodes/default.long_events_distinct_countries/upstream/", + ) + data = response.json() + assert {node["name"] for node in data} == { + "default.event_source", + "default.long_events", + } + + def test_list_node_dag(self, client_with_examples: TestClient): + """ + Test getting the DAG for a node + """ + response = client_with_examples.get( + "/nodes/default.long_events_distinct_countries/dag", + ) + data = response.json() + assert {node["name"] for node in data} == { + "default.event_source", + "default.long_events", + "default.long_events_distinct_countries", + } + + response = client_with_examples.get("/nodes/default.num_repair_orders/dag") + data = response.json() + assert {node["name"] for node in data} == { + "default.dispatcher", + "default.hard_hat", + "default.municipality_dim", + "default.num_repair_orders", + "default.repair_order", + "default.repair_orders", + "default.us_state", + } + + def test_node_column_lineage(self, client_with_examples: TestClient): + """ + Test endpoint to retrieve a node's column-level lineage + """ + response = client_with_examples.get( + "/nodes/default.num_repair_orders/lineage/", + ) + assert response.json() == [ + { + "column_name": "default_DOT_num_repair_orders", + "node_name": "default.num_repair_orders", + "node_type": "metric", + "display_name": "Default: Num Repair Orders", + "lineage": [ + { + "column_name": "repair_order_id", + "node_name": "default.repair_orders", + "node_type": "source", + "display_name": "Default: Repair Orders", + "lineage": [], + }, + ], + }, + ] + + client_with_examples.post( + "/nodes/metric/", + json={ + "name": "default.discounted_repair_orders", + "query": ( + """ + SELECT + cast(sum(if(discount > 0.0, 1, 0)) as double) / count(repair_order_id) + FROM default.repair_order_details + """ + ), + "mode": "published", + "description": "Discounted Repair Orders", + }, + ) + response = client_with_examples.get( + "/nodes/default.discounted_repair_orders/lineage/", + ) + assert response.json() == [ + { + "column_name": "default_DOT_discounted_repair_orders", + "node_name": "default.discounted_repair_orders", + "node_type": "metric", + "display_name": "Default: Discounted Repair Orders", + "lineage": [ + { + "column_name": "repair_order_id", + "node_name": "default.repair_order_details", + "node_type": "source", + "display_name": "Default: Repair Order Details", + "lineage": [], + }, + { + "column_name": "discount", + "node_name": "default.repair_order_details", + "node_type": "source", + "display_name": "Default: Repair Order Details", + "lineage": [], + }, + ], + }, + ] + + def test_revalidating_existing_nodes(self, client_with_examples: TestClient): + """ + Test revalidating all example nodes and confirm that they are set to valid + """ + for node in client_with_examples.get("/nodes/").json(): + status = client_with_examples.post( + f"/nodes/{node}/validate/", + ).json()["status"] + assert status == "valid" + # Confirm that they still show as valid server-side + for node in client_with_examples.get("/nodes/").json(): + node = client_with_examples.get(f"/nodes/{node}").json() + assert node["status"] == "valid" + + def test_lineage_on_complex_transforms(self, client_with_examples: TestClient): + """ + Test metric lineage on more complex transforms and metrics + """ + response = client_with_examples.get("/nodes/default.regional_level_agg/").json() + assert response["columns"] == [ + { + "name": "us_region_id", + "type": "int", + "attributes": [ + {"attribute_type": {"namespace": "system", "name": "primary_key"}}, + ], + "dimension": None, + }, + { + "name": "state_name", + "type": "string", + "attributes": [ + {"attribute_type": {"namespace": "system", "name": "primary_key"}}, + ], + "dimension": None, + }, + { + "name": "location_hierarchy", + "type": "string", + "attributes": [], + "dimension": None, + }, + { + "name": "order_year", + "type": "int", + "attributes": [ + {"attribute_type": {"namespace": "system", "name": "primary_key"}}, + ], + "dimension": None, + }, + { + "name": "order_month", + "type": "int", + "attributes": [ + {"attribute_type": {"namespace": "system", "name": "primary_key"}}, + ], + "dimension": None, + }, + { + "name": "order_day", + "type": "int", + "attributes": [ + {"attribute_type": {"namespace": "system", "name": "primary_key"}}, + ], + "dimension": None, + }, + { + "name": "completed_repairs", + "type": "bigint", + "attributes": [], + "dimension": None, + }, + { + "name": "total_repairs_dispatched", + "type": "bigint", + "attributes": [], + "dimension": None, + }, + { + "name": "total_amount_in_region", + "type": "double", + "attributes": [], + "dimension": None, + }, + { + "name": "avg_repair_amount_in_region", + "type": "double", + "attributes": [], + "dimension": None, + }, + { + "name": "avg_dispatch_delay", + "type": "double", + "attributes": [], + "dimension": None, + }, + { + "name": "unique_contractors", + "type": "bigint", + "attributes": [], + "dimension": None, + }, + ] + + response = client_with_examples.get( + "/nodes/default.regional_repair_efficiency/", + ).json() + assert response["columns"] == [ + { + "attributes": [], + "dimension": None, + "name": "default_DOT_regional_repair_efficiency", + "type": "double", + }, + ] + response = client_with_examples.get( + "/nodes/default.regional_repair_efficiency/lineage/", + ).json() + assert response == [ + { + "column_name": "default_DOT_regional_repair_efficiency", + "node_name": "default.regional_repair_efficiency", + "node_type": "metric", + "display_name": "Default: Regional Repair Efficiency", + "lineage": [ + { + "column_name": "total_amount_nationwide", + "node_name": "default.national_level_agg", + "node_type": "transform", + "display_name": "Default: National Level Agg", + "lineage": [ + { + "column_name": "quantity", + "node_name": "default.repair_order_details", + "node_type": "source", + "display_name": "Default: Repair Order Details", + "lineage": [], + }, + { + "column_name": "price", + "node_name": "default.repair_order_details", + "node_type": "source", + "display_name": "Default: Repair Order Details", + "lineage": [], + }, + ], + }, + { + "column_name": "total_amount_in_region", + "node_name": "default.regional_level_agg", + "node_type": "transform", + "display_name": "Default: Regional Level Agg", + "lineage": [ + { + "column_name": "quantity", + "node_name": "default.repair_order_details", + "node_type": "source", + "display_name": "Default: Repair Order Details", + "lineage": [], + }, + { + "column_name": "price", + "node_name": "default.repair_order_details", + "node_type": "source", + "display_name": "Default: Repair Order Details", + "lineage": [], + }, + ], + }, + { + "column_name": "total_repairs_dispatched", + "node_name": "default.regional_level_agg", + "node_type": "transform", + "display_name": "Default: Regional Level Agg", + "lineage": [ + { + "column_name": "repair_order_id", + "node_name": "default.repair_orders", + "node_type": "source", + "display_name": "Default: Repair Orders", + "lineage": [], + }, + ], + }, + { + "column_name": "completed_repairs", + "node_name": "default.regional_level_agg", + "node_type": "transform", + "display_name": "Default: Regional Level Agg", + "lineage": [ + { + "column_name": "repair_order_id", + "node_name": "default.repair_orders", + "node_type": "source", + "display_name": "Default: Repair Orders", + "lineage": [], + }, + { + "column_name": "dispatched_date", + "node_name": "default.repair_orders", + "node_type": "source", + "display_name": "Default: Repair Orders", + "lineage": [], + }, + ], + }, + ], + }, + ] + + +def test_node_similarity(session: Session, client: TestClient): + """ + Test determining node similarity based on their queries + """ + source_data = Node( + name="source_data", + type=NodeType.SOURCE, + current_version="1", + ) + source_data_rev = NodeRevision( + node=source_data, + version="1", + name=source_data.name, + type=source_data.type, + ) + a_transform = Node( + name="a_transform", + type=NodeType.TRANSFORM, + current_version="1", + ) + a_transform_rev = NodeRevision( + name=a_transform.name, + node=a_transform, + version="1", + query="SELECT 1 as num", + type=a_transform.type, + columns=[ + Column(name="num", type=IntegerType()), + ], + ) + another_transform = Node( + name="another_transform", + type=NodeType.TRANSFORM, + current_version="1", + ) + another_transform_rev = NodeRevision( + name=another_transform.name, + node=another_transform, + version="1", + query="SELECT 1 as num", + type=another_transform.type, + columns=[ + Column(name="num", type=IntegerType()), + ], + ) + yet_another_transform = Node( + name="yet_another_transform", + type=NodeType.TRANSFORM, + current_version="1", + ) + yet_another_transform_rev = NodeRevision( + name=yet_another_transform.name, + node=yet_another_transform, + version="1", + query="SELECT 2 as num", + type=yet_another_transform.type, + columns=[ + Column(name="num", type=IntegerType()), + ], + ) + session.add(source_data_rev) + session.add(a_transform_rev) + session.add(another_transform_rev) + session.add(yet_another_transform_rev) + session.commit() + + response = client.get("/nodes/similarity/a_transform/another_transform") + assert response.status_code == 200 + data = response.json() + assert data["similarity"] == 1.0 + + response = client.get("/nodes/similarity/a_transform/yet_another_transform") + assert response.status_code == 200 + data = response.json() + assert data["similarity"] == 0.75 + + response = client.get("/nodes/similarity/yet_another_transform/another_transform") + assert response.status_code == 200 + data = response.json() + assert data["similarity"] == 0.75 + + # Check that the proper error is raised when using a source node + response = client.get("/nodes/similarity/a_transform/source_data") + assert response.status_code == 409 + data = response.json() + assert data == { + "message": "Cannot determine similarity of source nodes", + "errors": [], + "warnings": [], + } + + +def test_resolving_downstream_status(client_with_examples: TestClient) -> None: + """ + Test creating and updating a source node + """ + # Create draft transform and metric nodes with missing parents + transform1 = { + "name": "default.comments_by_migrated_users", + "description": "Comments by users who have already migrated", + "query": "SELECT id, user_id FROM default.comments WHERE text LIKE '%migrated%'", + "mode": "draft", + } + + transform2 = { + "name": "default.comments_by_users_pending_a_migration", + "description": "Comments by users who have a migration pending", + "query": "SELECT id, user_id FROM default.comments WHERE text LIKE '%migration pending%'", + "mode": "draft", + } + + transform3 = { + "name": "default.comments_by_users_partially_migrated", + "description": "Comments by users are partially migrated", + "query": ( + "SELECT p.id, p.user_id FROM default.comments_by_users_pending_a_migration p " + "INNER JOIN default.comments_by_migrated_users m ON p.user_id = m.user_id" + ), + "mode": "draft", + } + + transform4 = { + "name": "default.comments_by_banned_users", + "description": "Comments by users are partially migrated", + "query": ( + "SELECT id, user_id FROM default.comments AS comment " + "INNER JOIN default.banned_users AS banned_users " + "ON comments.user_id = banned_users.banned_user_id" + ), + "mode": "draft", + } + + transform5 = { + "name": "default.comments_by_users_partially_migrated_sample", + "description": "Sample of comments by users are partially migrated", + "query": "SELECT id, user_id, foo FROM default.comments_by_users_partially_migrated", + "mode": "draft", + } + + metric1 = { + "name": "default.number_of_migrated_users", + "description": "Number of migrated users", + "query": "SELECT COUNT(DISTINCT user_id) FROM default.comments_by_migrated_users", + "mode": "draft", + } + + metric2 = { + "name": "default.number_of_users_with_pending_migration", + "description": "Number of users with a migration pending", + "query": ( + "SELECT COUNT(DISTINCT user_id) FROM " + "default.comments_by_users_pending_a_migration" + ), + "mode": "draft", + } + + metric3 = { + "name": "default.number_of_users_partially_migrated", + "description": "Number of users partially migrated", + "query": "SELECT COUNT(DISTINCT user_id) FROM default.comments_by_users_partially_migrated", + "mode": "draft", + } + + for node, node_type in [ + (transform1, NodeType.TRANSFORM), + (transform2, NodeType.TRANSFORM), + (transform3, NodeType.TRANSFORM), + (transform4, NodeType.TRANSFORM), + (transform5, NodeType.TRANSFORM), + (metric1, NodeType.METRIC), + (metric2, NodeType.METRIC), + (metric3, NodeType.METRIC), + ]: + response = client_with_examples.post( + f"/nodes/{node_type.value}/", + json=node, + ) + assert response.status_code == 201 + data = response.json() + assert data["name"] == node["name"] + assert data["mode"] == node["mode"] + assert data["status"] == "invalid" + + # Add the missing parent + missing_parent_node = { + "name": "default.comments", + "description": "A fact table with comments", + "columns": [ + {"name": "id", "type": "int"}, + {"name": "user_id", "type": "int"}, + {"name": "timestamp", "type": "timestamp"}, + {"name": "text", "type": "string"}, + ], + "mode": "published", + "catalog": "public", + "schema_": "basic", + "table": "comments", + } + + response = client_with_examples.post( + "/nodes/source/", + json=missing_parent_node, + ) + assert response.status_code == 200 + data = response.json() + assert data["name"] == missing_parent_node["name"] + assert data["mode"] == missing_parent_node["mode"] + + # Check that downstream nodes have now been switched to a "valid" status + for node in [transform1, transform2, transform3, metric1, metric2, metric3]: + response = client_with_examples.get(f"/nodes/{node['name']}/") + assert response.status_code == 200 + data = response.json() + assert data["name"] == node["name"] + assert data["mode"] == node["mode"] # make sure the mode hasn't been changed + assert ( + data["status"] == "valid" + ) # make sure the node's status has been updated to valid + + # Check that nodes still not valid have an invalid status + for node in [transform4, transform5]: + response = client_with_examples.get(f"/nodes/{node['name']}/") + assert response.status_code == 200 + data = response.json() + assert data["name"] == node["name"] + assert data["mode"] == node["mode"] # make sure the mode hasn't been changed + assert data["status"] == "invalid" + + +def test_decompose_expression(): + """ + Verify metric expression decomposition into measures for cubes + """ + res = decompose_expression(ast.Number(value=5.5)) + assert res == (ast.Number(value=5.5), []) + + # Decompose `avg(orders)` + res = decompose_expression( + ast.Function(ast.Name("avg"), args=[ast.Column(ast.Name("orders"))]), + ) + assert str(res[0]) == "sum(orders_sum) / count(orders_count)" + assert [measure.alias_or_name.name for measure in res[1]] == [ + "orders_sum", + "orders_count", + ] + + # Decompose `avg(orders) + 5.5` + res = decompose_expression( + ast.BinaryOp( + left=ast.Function(ast.Name("avg"), args=[ast.Column(ast.Name("orders"))]), + right=ast.Number(value=5.5), + op=ast.BinaryOpKind.Plus, + ), + ) + assert str(res[0]) == "sum(orders_sum) / count(orders_count) + 5.5" + assert [measure.alias_or_name.name for measure in res[1]] == [ + "orders_sum", + "orders_count", + ] + + # Decompose `max(avg(orders_a) + avg(orders_b))` + res = decompose_expression( + ast.Function( + ast.Name("max"), + args=[ + ast.BinaryOp( + op=ast.BinaryOpKind.Plus, + left=ast.Function( + ast.Name("avg"), + args=[ast.Column(ast.Name("orders_a"))], + ), + right=ast.Function( + ast.Name("avg"), + args=[ast.Column(ast.Name("orders_b"))], + ), + ), + ], + ), + ) + assert ( + str(res[0]) == "max(sum(orders_a_sum) / count(orders_a_count) " + "+ sum(orders_b_sum) / count(orders_b_count))" + ) + + # Decompose `sum(max(orders))` + res = decompose_expression( + ast.Function( + ast.Name("sum"), + args=[ + ast.Function( + ast.Name("max"), + args=[ast.Column(ast.Name("orders"))], + ), + ], + ), + ) + assert str(res[0]) == "sum(max(orders_max))" + assert [measure.alias_or_name.name for measure in res[1]] == ["orders_max"] + + # Decompose `(max(orders) + min(validations))/sum(total)` + res = decompose_expression( + ast.BinaryOp( + left=ast.BinaryOp( + left=ast.Function( + ast.Name("max"), + args=[ast.Column(ast.Name("orders"))], + ), + right=ast.Function( + ast.Name("min"), + args=[ast.Column(ast.Name("validations"))], + ), + op=ast.BinaryOpKind.Plus, + ), + right=ast.Function(ast.Name("sum"), args=[ast.Column(ast.Name("total"))]), + op=ast.BinaryOpKind.Divide, + ), + ) + assert str(res[0]) == "max(orders_max) + min(validations_min) / sum(total_sum)" + assert [measure.alias_or_name.name for measure in res[1]] == [ + "orders_max", + "validations_min", + "total_sum", + ] + + # Decompose `cast(sum(coalesce(has_ordered, 0.0)) as double)/count(total)` + res = decompose_expression( + ast.BinaryOp( + left=ast.Cast( + expression=ast.Function( + name=ast.Name("sum"), + args=[ + ast.Function( + ast.Name("coalesce"), + args=[ast.Column(ast.Name("has_ordered")), ast.Number(0.0)], + ), + ], + ), + data_type=types.DoubleType(), + ), + right=ast.Function( + name=ast.Name("sum"), + args=[ast.Column(ast.Name("total"))], + ), + op=ast.BinaryOpKind.Divide, + ), + ) + assert str(res[0]) == "sum(has_ordered_sum) / sum(total_sum)" + assert [measure.alias_or_name.name for measure in res[1]] == [ + "has_ordered_sum", + "total_sum", + ] diff --git a/datajunction-server/tests/api/sql_test.py b/datajunction-server/tests/api/sql_test.py new file mode 100644 index 000000000..2a6fc8e8a --- /dev/null +++ b/datajunction-server/tests/api/sql_test.py @@ -0,0 +1,1086 @@ +"""Tests for the /sql/ endpoint""" +# pylint: disable=line-too-long,too-many-lines +# pylint: disable=C0302 +import pytest +from sqlmodel import Session +from starlette.testclient import TestClient + +from datajunction_server.models import Column, Database, Node +from datajunction_server.models.node import NodeRevision, NodeType +from datajunction_server.sql.parsing.types import StringType +from tests.sql.utils import compare_query_strings + + +def test_sql( + session: Session, + client: TestClient, +) -> None: + """ + Test ``GET /sql/{name}/``. + """ + database = Database(name="test", URI="blah://", tables=[]) + + source_node = Node(name="my_table", type=NodeType.SOURCE, current_version="1") + source_node_rev = NodeRevision( + name=source_node.name, + node=source_node, + version="1", + schema_="rev", + table="my_table", + columns=[Column(name="one", type=StringType())], + type=NodeType.SOURCE, + ) + + node = Node(name="a-metric", type=NodeType.METRIC, current_version="1") + node_revision = NodeRevision( + name=node.name, + node=node, + version="1", + query="SELECT COUNT(*) FROM my_table", + type=NodeType.METRIC, + ) + session.add(database) + session.add(node_revision) + session.add(source_node_rev) + session.commit() + + response = client.get("/sql/a-metric/").json() + assert compare_query_strings( + response["sql"], + "SELECT COUNT(*) col0 \n FROM rev.my_table AS my_table\n", + ) + assert response["columns"] == [{"name": "col0", "type": "bigint"}] + assert response["dialect"] is None + + +@pytest.mark.parametrize( + "node_name, dimensions, filters, sql", + [ + # querying on source node with filter on joinable dimension + ( + "default.repair_orders", + [], + ["default.hard_hat.state='CA'"], + """ + SELECT default_DOT_hard_hat.state, + default_DOT_repair_orders.dispatched_date, + default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.order_date, + default_DOT_repair_orders.repair_order_id, + default_DOT_repair_orders.required_date + FROM roads.repair_orders AS default_DOT_repair_orders + LEFT OUTER JOIN ( + SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders + ) AS default_DOT_repair_order ON default_DOT_repair_orders.repair_order_id = default_DOT_repair_order.repair_order_id + LEFT OUTER JOIN ( + SELECT default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats + ) AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + WHERE default_DOT_hard_hat.state = 'CA' + """, + ), + # querying source node with filters directly on the node + ( + "default.repair_orders", + [], + ["default.repair_orders.order_date='2009-08-14'"], + """ + SELECT default_DOT_repair_orders.dispatched_date, + default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.order_date, + default_DOT_repair_orders.repair_order_id, + default_DOT_repair_orders.required_date + FROM roads.repair_orders AS default_DOT_repair_orders + WHERE default_DOT_repair_orders.order_date = '2009-08-14' + """, + ), + # querying transform node with filters on joinable dimension + ( + "default.long_events", + [], + ["default.country_dim.events_cnt >= 20"], + """ + SELECT default_DOT_event_source.country, + default_DOT_country_dim.events_cnt, + default_DOT_event_source.device_id, + default_DOT_event_source.event_id, + default_DOT_event_source.event_latency + FROM logs.log_events AS default_DOT_event_source + LEFT OUTER JOIN (SELECT default_DOT_event_source.country, + COUNT( DISTINCT default_DOT_event_source.event_id) AS events_cnt + FROM logs.log_events AS default_DOT_event_source + GROUP BY default_DOT_event_source.country) AS default_DOT_country_dim + ON default_DOT_event_source.country = default_DOT_country_dim.country + WHERE default_DOT_event_source.event_latency > 1000000 + AND default_DOT_country_dim.events_cnt >= 20 + """, + ), + # querying transform node with filters directly on the node + ( + "default.long_events", + [], + ["default.event_source.device_id = 'Android'"], + """ + SELECT default_DOT_event_source.country, + default_DOT_event_source.device_id, + default_DOT_event_source.event_id, + default_DOT_event_source.event_latency + FROM logs.log_events AS default_DOT_event_source + WHERE default_DOT_event_source.event_latency > 1000000 + AND default_DOT_event_source.device_id = 'Android' + """, + ), + ( + "default.municipality", + [], + ["default.municipality.state_id = 'CA'"], + """ + SELECT default_DOT_municipality.contact_name, + default_DOT_municipality.contact_title, + default_DOT_municipality.state_id, + default_DOT_municipality.local_region, + default_DOT_municipality.municipality_id, + default_DOT_municipality.phone + FROM roads.municipality AS default_DOT_municipality + WHERE default_DOT_municipality.state_id = 'CA' + """, + ), + ( + "default.num_repair_orders", + [], + [], + """ + SELECT count(default_DOT_repair_orders.repair_order_id) default_DOT_num_repair_orders + FROM roads.repair_orders AS default_DOT_repair_orders + """, + ), + ( + "default.num_repair_orders", + ["default.hard_hat.state"], + ["default.repair_orders.dispatcher_id=1", "default.hard_hat.state='AZ'"], + """ + SELECT default_DOT_hard_hat.state, + count(default_DOT_repair_orders.repair_order_id) default_DOT_num_repair_orders + FROM roads.repair_orders AS default_DOT_repair_orders + LEFT OUTER JOIN ( + SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders + ) AS default_DOT_repair_order ON default_DOT_repair_orders.repair_order_id = default_DOT_repair_order.repair_order_id + LEFT OUTER JOIN ( + SELECT default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats + ) AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + WHERE default_DOT_repair_orders.dispatcher_id = 1 + AND default_DOT_hard_hat.state = 'AZ' + GROUP BY default_DOT_hard_hat.state + """, + ), + ( + "default.num_repair_orders", + [ + "default.hard_hat.city", + "default.hard_hat.last_name", + "default.dispatcher.company_name", + "default.municipality_dim.local_region", + ], + [ + "default.repair_orders.dispatcher_id=1", + "default.hard_hat.state != 'AZ'", + "default.dispatcher.phone = '4082021022'", + "default.repair_orders.order_date >= '2020-01-01'", + ], + """ + SELECT default_DOT_dispatcher.company_name, + default_DOT_hard_hat.city, + default_DOT_hard_hat.last_name, + default_DOT_municipality_dim.local_region, + count(default_DOT_repair_orders.repair_order_id) default_DOT_num_repair_orders + FROM roads.repair_orders AS default_DOT_repair_orders + LEFT OUTER JOIN ( + SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders + ) AS default_DOT_repair_order ON default_DOT_repair_orders.repair_order_id = default_DOT_repair_order.repair_order_id + LEFT OUTER JOIN ( + SELECT default_DOT_dispatchers.company_name, + default_DOT_dispatchers.dispatcher_id, + default_DOT_dispatchers.phone + FROM roads.dispatchers AS default_DOT_dispatchers + ) AS default_DOT_dispatcher ON default_DOT_repair_order.dispatcher_id = default_DOT_dispatcher.dispatcher_id + LEFT OUTER JOIN ( + SELECT default_DOT_hard_hats.city, + default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.last_name, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats + ) AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + LEFT OUTER JOIN ( + SELECT default_DOT_municipality.local_region, + default_DOT_municipality.municipality_id AS municipality_id + FROM roads.municipality AS default_DOT_municipality + LEFT JOIN roads.municipality_municipality_type AS default_DOT_municipality_municipality_type ON default_DOT_municipality.municipality_id = default_DOT_municipality_municipality_type.municipality_id + LEFT JOIN roads.municipality_type AS default_DOT_municipality_type ON default_DOT_municipality_municipality_type.municipality_type_id = default_DOT_municipality_type.municipality_type_desc + ) AS default_DOT_municipality_dim ON default_DOT_repair_order.municipality_id = default_DOT_municipality_dim.municipality_id + WHERE default_DOT_repair_orders.dispatcher_id = 1 + AND default_DOT_hard_hat.state != 'AZ' + AND default_DOT_dispatcher.phone = '4082021022' + AND default_DOT_repair_orders.order_date >= '2020-01-01' + GROUP BY default_DOT_hard_hat.city, + default_DOT_hard_hat.last_name, + default_DOT_dispatcher.company_name, + default_DOT_municipality_dim.local_region + """, + ), + # metric with second-order dimension + ( + "default.avg_repair_price", + ["default.hard_hat.city"], + [], + """ + SELECT default_DOT_hard_hat.city, + avg(default_DOT_repair_order_details.price) AS default_DOT_avg_repair_price + FROM roads.repair_order_details AS default_DOT_repair_order_details + LEFT OUTER JOIN ( + SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders + ) AS default_DOT_repair_order ON default_DOT_repair_order_details.repair_order_id = default_DOT_repair_order.repair_order_id + LEFT OUTER JOIN ( + SELECT default_DOT_hard_hats.city, + default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats + ) AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + GROUP BY default_DOT_hard_hat.city + """, + ), + # metric with multiple nth order dimensions that can share some of the joins + ( + "default.avg_repair_price", + ["default.hard_hat.city", "default.dispatcher.company_name"], + [], + """ + SELECT avg(default_DOT_repair_order_details.price) AS default_DOT_avg_repair_price, + default_DOT_dispatcher.company_name, + default_DOT_hard_hat.city + FROM roads.repair_order_details AS default_DOT_repair_order_details LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders) AS default_DOT_repair_order ON default_DOT_repair_order_details.repair_order_id = default_DOT_repair_order.repair_order_id + LEFT OUTER JOIN (SELECT default_DOT_dispatchers.company_name, + default_DOT_dispatchers.dispatcher_id + FROM roads.dispatchers AS default_DOT_dispatchers) AS default_DOT_dispatcher ON default_DOT_repair_order.dispatcher_id = default_DOT_dispatcher.dispatcher_id + LEFT OUTER JOIN (SELECT default_DOT_hard_hats.city, + default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats) AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + GROUP BY default_DOT_hard_hat.city, default_DOT_dispatcher.company_name + """, + ), + # dimension with aliased join key should just use the alias directly + ( + "default.num_repair_orders", + ["default.us_state.state_region_description"], + [], + """ + SELECT default_DOT_us_state.state_region_description, + count(default_DOT_repair_orders.repair_order_id) default_DOT_num_repair_orders + FROM roads.repair_orders AS default_DOT_repair_orders + LEFT OUTER JOIN ( + SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders + ) AS default_DOT_repair_order ON default_DOT_repair_orders.repair_order_id = default_DOT_repair_order.repair_order_id + LEFT OUTER JOIN ( + SELECT default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats + ) AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + LEFT OUTER JOIN ( + SELECT default_DOT_us_states.state_id, + default_DOT_us_region.us_region_description AS state_region_description, + default_DOT_us_states.state_abbr AS state_short + FROM roads.us_states AS default_DOT_us_states + LEFT JOIN roads.us_region AS default_DOT_us_region ON default_DOT_us_states.state_region = default_DOT_us_region.us_region_id + ) AS default_DOT_us_state ON default_DOT_hard_hat.state = default_DOT_us_state.state_short + GROUP BY default_DOT_us_state.state_region_description + """, + ), + ], +) +def test_sql_with_filters( + node_name, + dimensions, + filters, + sql, + client_with_examples: TestClient, +): + """ + Test ``GET /sql/{node_name}/`` with various filters and dimensions. + """ + response = client_with_examples.get( + f"/sql/{node_name}/", + params={"dimensions": dimensions, "filters": filters}, + ) + data = response.json() + assert compare_query_strings(data["sql"], sql) + + +@pytest.mark.parametrize( + "node_name, dimensions, filters, orderby, sql", + [ + # querying on source node with filter on joinable dimension + ( + "foo.bar.repair_orders", + [], + ["foo.bar.hard_hat.state='CA'"], + [], + """ + SELECT foo_DOT_bar_DOT_repair_orders.dispatched_date, + foo_DOT_bar_DOT_repair_orders.dispatcher_id, + foo_DOT_bar_DOT_hard_hat.state, + foo_DOT_bar_DOT_repair_orders.hard_hat_id, + foo_DOT_bar_DOT_repair_orders.municipality_id, + foo_DOT_bar_DOT_repair_orders.order_date, + foo_DOT_bar_DOT_repair_orders.repair_order_id, + foo_DOT_bar_DOT_repair_orders.required_date + FROM roads.repair_orders AS foo_DOT_bar_DOT_repair_orders + LEFT OUTER JOIN ( + SELECT foo_DOT_bar_DOT_repair_orders.dispatcher_id, + foo_DOT_bar_DOT_repair_orders.hard_hat_id, + foo_DOT_bar_DOT_repair_orders.municipality_id, + foo_DOT_bar_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS foo_DOT_bar_DOT_repair_orders + ) AS foo_DOT_bar_DOT_repair_order ON foo_DOT_bar_DOT_repair_orders.repair_order_id = foo_DOT_bar_DOT_repair_order.repair_order_id + LEFT OUTER JOIN ( + SELECT foo_DOT_bar_DOT_hard_hats.hard_hat_id, + foo_DOT_bar_DOT_hard_hats.state + FROM roads.hard_hats AS foo_DOT_bar_DOT_hard_hats + ) AS foo_DOT_bar_DOT_hard_hat ON foo_DOT_bar_DOT_repair_order.hard_hat_id = foo_DOT_bar_DOT_hard_hat.hard_hat_id + WHERE foo_DOT_bar_DOT_hard_hat.state = 'CA' + """, + ), + # querying source node with filters directly on the node + ( + "foo.bar.repair_orders", + [], + ["foo.bar.repair_orders.order_date='2009-08-14'"], + [], + """ + SELECT + foo_DOT_bar_DOT_repair_orders.dispatched_date, + foo_DOT_bar_DOT_repair_orders.dispatcher_id, + foo_DOT_bar_DOT_repair_orders.hard_hat_id, + foo_DOT_bar_DOT_repair_orders.municipality_id, + foo_DOT_bar_DOT_repair_orders.order_date, + foo_DOT_bar_DOT_repair_orders.repair_order_id, + foo_DOT_bar_DOT_repair_orders.required_date + FROM roads.repair_orders AS foo_DOT_bar_DOT_repair_orders + WHERE + foo_DOT_bar_DOT_repair_orders.order_date = '2009-08-14' + """, + ), + ( + "foo.bar.num_repair_orders", + [], + [], + [], + """ + SELECT + count(foo_DOT_bar_DOT_repair_orders.repair_order_id) AS foo_DOT_bar_DOT_num_repair_orders + FROM roads.repair_orders AS foo_DOT_bar_DOT_repair_orders + """, + ), + ( + "foo.bar.num_repair_orders", + ["foo.bar.hard_hat.state"], + ["foo.bar.repair_orders.dispatcher_id=1", "foo.bar.hard_hat.state='AZ'"], + [], + """ + SELECT foo_DOT_bar_DOT_hard_hat.state, + count(foo_DOT_bar_DOT_repair_orders.repair_order_id) AS foo_DOT_bar_DOT_num_repair_orders + FROM roads.repair_orders AS foo_DOT_bar_DOT_repair_orders + LEFT OUTER JOIN ( + SELECT foo_DOT_bar_DOT_repair_orders.dispatcher_id, + foo_DOT_bar_DOT_repair_orders.hard_hat_id, + foo_DOT_bar_DOT_repair_orders.municipality_id, + foo_DOT_bar_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS foo_DOT_bar_DOT_repair_orders + ) AS foo_DOT_bar_DOT_repair_order ON foo_DOT_bar_DOT_repair_orders.repair_order_id = foo_DOT_bar_DOT_repair_order.repair_order_id + LEFT OUTER JOIN ( + SELECT foo_DOT_bar_DOT_hard_hats.hard_hat_id, + foo_DOT_bar_DOT_hard_hats.state + FROM roads.hard_hats AS foo_DOT_bar_DOT_hard_hats + ) AS foo_DOT_bar_DOT_hard_hat ON foo_DOT_bar_DOT_repair_order.hard_hat_id = foo_DOT_bar_DOT_hard_hat.hard_hat_id + WHERE foo_DOT_bar_DOT_repair_orders.dispatcher_id = 1 + AND foo_DOT_bar_DOT_hard_hat.state = 'AZ' + GROUP BY foo_DOT_bar_DOT_hard_hat.state + """, + ), + ( + "foo.bar.num_repair_orders", + [ + "foo.bar.hard_hat.city", + "foo.bar.hard_hat.last_name", + "foo.bar.dispatcher.company_name", + "foo.bar.municipality_dim.local_region", + ], + [ + "foo.bar.repair_orders.dispatcher_id=1", + "foo.bar.hard_hat.state != 'AZ'", + "foo.bar.dispatcher.phone = '4082021022'", + "foo.bar.repair_orders.order_date >= '2020-01-01'", + ], + ["foo.bar.hard_hat.last_name"], + """ + SELECT foo_DOT_bar_DOT_dispatcher.company_name, + foo_DOT_bar_DOT_hard_hat.city, + foo_DOT_bar_DOT_hard_hat.last_name, + foo_DOT_bar_DOT_municipality_dim.local_region, + count(foo_DOT_bar_DOT_repair_orders.repair_order_id) AS foo_DOT_bar_DOT_num_repair_orders + FROM roads.repair_orders AS foo_DOT_bar_DOT_repair_orders + LEFT OUTER JOIN ( + SELECT foo_DOT_bar_DOT_repair_orders.dispatcher_id, + foo_DOT_bar_DOT_repair_orders.hard_hat_id, + foo_DOT_bar_DOT_repair_orders.municipality_id, + foo_DOT_bar_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS foo_DOT_bar_DOT_repair_orders + ) AS foo_DOT_bar_DOT_repair_order ON foo_DOT_bar_DOT_repair_orders.repair_order_id = foo_DOT_bar_DOT_repair_order.repair_order_id + LEFT OUTER JOIN ( + SELECT foo_DOT_bar_DOT_dispatchers.company_name, + foo_DOT_bar_DOT_dispatchers.dispatcher_id, + foo_DOT_bar_DOT_dispatchers.phone + FROM roads.dispatchers AS foo_DOT_bar_DOT_dispatchers + ) AS foo_DOT_bar_DOT_dispatcher ON foo_DOT_bar_DOT_repair_order.dispatcher_id = foo_DOT_bar_DOT_dispatcher.dispatcher_id + LEFT OUTER JOIN ( + SELECT foo_DOT_bar_DOT_hard_hats.city, + foo_DOT_bar_DOT_hard_hats.hard_hat_id, + foo_DOT_bar_DOT_hard_hats.last_name, + foo_DOT_bar_DOT_hard_hats.state + FROM roads.hard_hats AS foo_DOT_bar_DOT_hard_hats + ) AS foo_DOT_bar_DOT_hard_hat ON foo_DOT_bar_DOT_repair_order.hard_hat_id = foo_DOT_bar_DOT_hard_hat.hard_hat_id + LEFT OUTER JOIN ( + SELECT foo_DOT_bar_DOT_municipality.local_region, + foo_DOT_bar_DOT_municipality.municipality_id AS municipality_id + FROM roads.municipality AS foo_DOT_bar_DOT_municipality + LEFT JOIN roads.municipality_municipality_type AS foo_DOT_bar_DOT_municipality_municipality_type ON foo_DOT_bar_DOT_municipality.municipality_id = foo_DOT_bar_DOT_municipality_municipality_type.municipality_id + LEFT JOIN roads.municipality_type AS foo_DOT_bar_DOT_municipality_type ON foo_DOT_bar_DOT_municipality_municipality_type.municipality_type_id = foo_DOT_bar_DOT_municipality_type.municipality_type_desc + ) AS foo_DOT_bar_DOT_municipality_dim ON foo_DOT_bar_DOT_repair_order.municipality_id = foo_DOT_bar_DOT_municipality_dim.municipality_id + WHERE foo_DOT_bar_DOT_repair_orders.dispatcher_id = 1 + AND foo_DOT_bar_DOT_hard_hat.state != 'AZ' + AND foo_DOT_bar_DOT_dispatcher.phone = '4082021022' + AND foo_DOT_bar_DOT_repair_orders.order_date >= '2020-01-01' + GROUP BY foo_DOT_bar_DOT_hard_hat.city, + foo_DOT_bar_DOT_hard_hat.last_name, + foo_DOT_bar_DOT_dispatcher.company_name, + foo_DOT_bar_DOT_municipality_dim.local_region + ORDER BY foo_DOT_bar_DOT_hard_hat.last_name + """, + ), + ( + "foo.bar.avg_repair_price", + ["foo.bar.hard_hat.city"], + [], + [], + """ + SELECT + avg(foo_DOT_bar_DOT_repair_order_details.price) foo_DOT_bar_DOT_avg_repair_price, + foo_DOT_bar_DOT_hard_hat.city + FROM roads.repair_order_details AS foo_DOT_bar_DOT_repair_order_details + LEFT OUTER JOIN ( + SELECT + foo_DOT_bar_DOT_repair_orders.dispatcher_id, + foo_DOT_bar_DOT_repair_orders.hard_hat_id, + foo_DOT_bar_DOT_repair_orders.municipality_id, + foo_DOT_bar_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS foo_DOT_bar_DOT_repair_orders + ) AS foo_DOT_bar_DOT_repair_order + ON foo_DOT_bar_DOT_repair_order_details.repair_order_id + = foo_DOT_bar_DOT_repair_order.repair_order_id + LEFT OUTER JOIN ( + SELECT + foo_DOT_bar_DOT_hard_hats.city, + foo_DOT_bar_DOT_hard_hats.hard_hat_id + FROM roads.hard_hats AS foo_DOT_bar_DOT_hard_hats + ) AS foo_DOT_bar_DOT_hard_hat + ON foo_DOT_bar_DOT_repair_order.hard_hat_id = foo_DOT_bar_DOT_hard_hat.hard_hat_id + GROUP BY foo_DOT_bar_DOT_hard_hat.city + """, + ), + ], +) +def test_sql_with_filters_on_namespaced_nodes( # pylint: disable=R0913 + node_name, + dimensions, + filters, + orderby, + sql, + client_with_examples: TestClient, +): + """ + Test ``GET /sql/{node_name}/`` with various filters and dimensions using a + version of the DJ roads database with namespaces. + """ + response = client_with_examples.get( + f"/sql/{node_name}/", + params={"dimensions": dimensions, "filters": filters, "orderby": orderby}, + ) + data = response.json() + assert compare_query_strings(data["sql"], sql) + + +def test_cross_join_unnest(client_with_examples: TestClient): + """ + Verify cross join unnest on a joined in dimension works + """ + client_with_examples.post( + "/nodes/basic.corrected_patches/columns/color_id/" + "?dimension=basic.paint_colors_trino&dimension_column=color_id", + ) + response = client_with_examples.get( + "/sql/basic.avg_luminosity_patches/", + params={ + "filters": [], + "dimensions": [ + "basic.paint_colors_trino.color_id", + "basic.paint_colors_trino.color_name", + ], + }, + ) + expected = """ + SELECT + paint_colors_trino.color_id, + basic_DOT_paint_colors_trino.color_name, + AVG(basic_DOT_corrected_patches.luminosity) AS cnt + FROM ( + SELECT + CAST(basic_DOT_patches.color_id AS VARCHAR) color_id, + basic_DOT_patches.color_name, + basic_DOT_patches.garishness, + basic_DOT_patches.luminosity, + basic_DOT_patches.opacity + FROM basic.patches AS basic_DOT_patches + ) AS basic_DOT_corrected_patches + LEFT OUTER JOIN ( + SELECT + t.color_name color_name, + t.color_id + FROM ( + SELECT + basic_DOT_murals.id, + basic_DOT_murals.colors + FROM basic.murals AS basic_DOT_murals + ) murals + CROSS JOIN UNNEST(murals.colors) t( color_id, color_name) + ) AS basic_DOT_paint_colors_trino + ON basic_DOT_corrected_patches.color_id = basic_DOT_paint_colors_trino.color_id + GROUP BY + paint_colors_trino.color_id, + basic_DOT_paint_colors_trino.color_name + """ + query = response.json()["sql"] + compare_query_strings(query, expected) + + +def test_lateral_view_explode(client_with_examples: TestClient): + """ + Verify lateral view explode on a joined in dimension works + """ + client_with_examples.post( + "/nodes/basic.corrected_patches/columns/color_id/" + "?dimension=basic.paint_colors_spark&dimension_column=color_id", + ) + response = client_with_examples.get( + "/sql/basic.avg_luminosity_patches/", + params={ + "filters": [], + "dimensions": [ + "basic.paint_colors_spark.color_id", + "basic.paint_colors_spark.color_name", + ], + "limit": 5, + }, + ) + expected = """ + SELECT + paint_colors_spark.color_id, + basic_DOT_paint_colors_spark.color_name, + AVG(basic_DOT_corrected_patches.luminosity) AS cnt + FROM ( + SELECT + CAST(basic_DOT_patches.color_id AS VARCHAR) color_id, + basic_DOT_patches.color_name, + basic_DOT_patches.garishness, + basic_DOT_patches.luminosity, + basic_DOT_patches.opacity + FROM basic.patches AS basic_DOT_patches + ) AS basic_DOT_corrected_patches + LEFT OUTER JOIN ( + SELECT + color_name color_name, + color_id + FROM ( + SELECT + basic_DOT_murals.id, + basic_DOT_murals.colors + FROM basic.murals AS basic_DOT_murals + ) murals + LATERAL VIEW EXPLODE(murals.colors) AS color_id, color_name + ) AS basic_DOT_paint_colors_spark + ON basic_DOT_corrected_patches.color_id = basic_DOT_paint_colors_trino.color_id + GROUP BY + paint_colors_spark.color_id, + basic_DOT_paint_colors_trino.color_name + + LIMIT 5 + """ + query = response.json()["sql"] + compare_query_strings(query, expected) + + +def test_get_sql_for_metrics_failures(client_with_examples: TestClient): + """ + Test failure modes when getting sql for multiple metrics. + """ + # Getting sql for no metrics fails appropriately + response = client_with_examples.get( + "/sql/", + params={ + "metrics": [], + "dimensions": ["default.account_type.account_type_name"], + "filters": [], + }, + ) + assert response.status_code == 422 + data = response.json() + assert data == { + "message": "At least one metric is required", + "errors": [], + "warnings": [], + } + + # Getting sql with no dimensions fails appropriately + response = client_with_examples.get( + "/sql/", + params={ + "metrics": ["default.number_of_account_types"], + "dimensions": [], + "filters": [], + }, + ) + assert response.status_code == 422 + data = response.json() + assert data == { + "message": "At least one dimension is required", + "errors": [], + "warnings": [], + } + + +def test_get_sql_for_metrics(client_with_examples: TestClient): + """ + Test getting sql for multiple metrics. + """ + response = client_with_examples.get( + "/sql/", + params={ + "metrics": ["default.discounted_orders_rate", "default.num_repair_orders"], + "dimensions": [ + "default.hard_hat.country", + "default.hard_hat.postal_code", + "default.hard_hat.city", + "default.hard_hat.state", + "default.dispatcher.company_name", + "default.municipality_dim.local_region", + ], + "filters": [], + "orderby": [ + "default.hard_hat.country", + "default.num_repair_orders", + "default.dispatcher.company_name", + "default.discounted_orders_rate", + ], + "limit": 100, + }, + ) + data = response.json() + expected_sql = """ + WITH + m0_default_DOT_discounted_orders_rate AS (SELECT default_DOT_dispatcher.company_name, + default_DOT_hard_hat.city, + default_DOT_hard_hat.country, + default_DOT_hard_hat.postal_code, + default_DOT_hard_hat.state, + default_DOT_municipality_dim.local_region, + CAST(sum(if(default_DOT_repair_order_details.discount > 0.0, 1, 0)) AS DOUBLE) / count(*) AS default_DOT_discounted_orders_rate + FROM roads.repair_order_details AS default_DOT_repair_order_details LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders) + AS default_DOT_repair_order ON default_DOT_repair_order_details.repair_order_id = default_DOT_repair_order.repair_order_id + LEFT OUTER JOIN (SELECT default_DOT_dispatchers.company_name, + default_DOT_dispatchers.dispatcher_id + FROM roads.dispatchers AS default_DOT_dispatchers) + AS default_DOT_dispatcher ON default_DOT_repair_order.dispatcher_id = default_DOT_dispatcher.dispatcher_id + LEFT OUTER JOIN (SELECT default_DOT_hard_hats.city, + default_DOT_hard_hats.country, + default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.postal_code, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats) + AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + LEFT OUTER JOIN (SELECT default_DOT_municipality.local_region, + default_DOT_municipality.municipality_id AS municipality_id + FROM roads.municipality AS default_DOT_municipality LEFT JOIN roads.municipality_municipality_type AS default_DOT_municipality_municipality_type ON default_DOT_municipality.municipality_id = default_DOT_municipality_municipality_type.municipality_id + LEFT JOIN roads.municipality_type AS default_DOT_municipality_type ON default_DOT_municipality_municipality_type.municipality_type_id = default_DOT_municipality_type.municipality_type_desc) + AS default_DOT_municipality_dim ON default_DOT_repair_order.municipality_id = default_DOT_municipality_dim.municipality_id + GROUP BY default_DOT_hard_hat.country, default_DOT_hard_hat.postal_code, default_DOT_hard_hat.city, default_DOT_hard_hat.state, default_DOT_dispatcher.company_name, default_DOT_municipality_dim.local_region + ), + m1_default_DOT_num_repair_orders AS (SELECT default_DOT_dispatcher.company_name, + default_DOT_hard_hat.city, + default_DOT_hard_hat.country, + default_DOT_hard_hat.postal_code, + default_DOT_hard_hat.state, + default_DOT_municipality_dim.local_region, + count(default_DOT_repair_orders.repair_order_id) default_DOT_num_repair_orders + FROM roads.repair_orders AS default_DOT_repair_orders LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders) + AS default_DOT_repair_order ON default_DOT_repair_orders.repair_order_id = default_DOT_repair_order.repair_order_id + LEFT OUTER JOIN (SELECT default_DOT_dispatchers.company_name, + default_DOT_dispatchers.dispatcher_id + FROM roads.dispatchers AS default_DOT_dispatchers) + AS default_DOT_dispatcher ON default_DOT_repair_order.dispatcher_id = default_DOT_dispatcher.dispatcher_id + LEFT OUTER JOIN (SELECT default_DOT_hard_hats.city, + default_DOT_hard_hats.country, + default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.postal_code, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats) + AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + LEFT OUTER JOIN (SELECT default_DOT_municipality.local_region, + default_DOT_municipality.municipality_id AS municipality_id + FROM roads.municipality AS default_DOT_municipality LEFT JOIN roads.municipality_municipality_type AS default_DOT_municipality_municipality_type ON default_DOT_municipality.municipality_id = default_DOT_municipality_municipality_type.municipality_id + LEFT JOIN roads.municipality_type AS default_DOT_municipality_type ON default_DOT_municipality_municipality_type.municipality_type_id = default_DOT_municipality_type.municipality_type_desc) + AS default_DOT_municipality_dim ON default_DOT_repair_order.municipality_id = default_DOT_municipality_dim.municipality_id + GROUP BY default_DOT_hard_hat.country, default_DOT_hard_hat.postal_code, default_DOT_hard_hat.city, default_DOT_hard_hat.state, default_DOT_dispatcher.company_name, default_DOT_municipality_dim.local_region + )SELECT m0_default_DOT_discounted_orders_rate.default_DOT_discounted_orders_rate, + m1_default_DOT_num_repair_orders.default_DOT_num_repair_orders, + COALESCE(m0_default_DOT_discounted_orders_rate.company_name, m1_default_DOT_num_repair_orders.company_name) company_name, + COALESCE(m0_default_DOT_discounted_orders_rate.city, m1_default_DOT_num_repair_orders.city) city, + COALESCE(m0_default_DOT_discounted_orders_rate.country, m1_default_DOT_num_repair_orders.country) country, + COALESCE(m0_default_DOT_discounted_orders_rate.postal_code, m1_default_DOT_num_repair_orders.postal_code) postal_code, + COALESCE(m0_default_DOT_discounted_orders_rate.state, m1_default_DOT_num_repair_orders.state) state, + COALESCE(m0_default_DOT_discounted_orders_rate.local_region, m1_default_DOT_num_repair_orders.local_region) local_region + FROM m0_default_DOT_discounted_orders_rate FULL OUTER JOIN m1_default_DOT_num_repair_orders ON m0_default_DOT_discounted_orders_rate.company_name = m1_default_DOT_num_repair_orders.company_name AND m0_default_DOT_discounted_orders_rate.city = m1_default_DOT_num_repair_orders.city AND m0_default_DOT_discounted_orders_rate.country = m1_default_DOT_num_repair_orders.country AND m0_default_DOT_discounted_orders_rate.postal_code = m1_default_DOT_num_repair_orders.postal_code AND m0_default_DOT_discounted_orders_rate.state = m1_default_DOT_num_repair_orders.state AND m0_default_DOT_discounted_orders_rate.local_region = m1_default_DOT_num_repair_orders.local_region + ORDER BY m0_default_DOT_discounted_orders_rate.country, m1_default_DOT_num_repair_orders.default_DOT_num_repair_orders, m0_default_DOT_discounted_orders_rate.company_name, m0_default_DOT_discounted_orders_rate.default_DOT_discounted_orders_rate + LIMIT 100 + """ + assert compare_query_strings(data["sql"], expected_sql) + assert data["columns"] == [ + {"name": "default_DOT_discounted_orders_rate", "type": "double"}, + {"name": "default_DOT_num_repair_orders", "type": "bigint"}, + {"name": "company_name", "type": "string"}, + {"name": "city", "type": "string"}, + {"name": "country", "type": "string"}, + {"name": "postal_code", "type": "string"}, + {"name": "state", "type": "string"}, + {"name": "local_region", "type": "string"}, + ] + + +def test_get_sql_including_dimension_ids(client_with_examples: TestClient): + """ + Test getting SQL when there are dimensions ids included + """ + response = client_with_examples.get( + "/sql/", + params={ + "metrics": ["default.avg_repair_price", "default.total_repair_cost"], + "dimensions": [ + "default.dispatcher.company_name", + "default.dispatcher.dispatcher_id", + ], + "filters": [], + }, + ) + assert response.status_code == 200 + data = response.json() + assert compare_query_strings( + data["sql"], + """ + WITH + m0_default_DOT_avg_repair_price AS (SELECT default_DOT_dispatcher.company_name, + default_DOT_dispatcher.dispatcher_id, + avg(default_DOT_repair_order_details.price) AS default_DOT_avg_repair_price + FROM roads.repair_order_details AS default_DOT_repair_order_details LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders) + AS default_DOT_repair_order ON default_DOT_repair_order_details.repair_order_id = default_DOT_repair_order.repair_order_id + LEFT OUTER JOIN (SELECT default_DOT_dispatchers.company_name, + default_DOT_dispatchers.dispatcher_id + FROM roads.dispatchers AS default_DOT_dispatchers) + AS default_DOT_dispatcher ON default_DOT_repair_order.dispatcher_id = default_DOT_dispatcher.dispatcher_id + GROUP BY default_DOT_dispatcher.company_name, default_DOT_dispatcher.dispatcher_id + ), + m1_default_DOT_total_repair_cost AS (SELECT default_DOT_dispatcher.company_name, + default_DOT_dispatcher.dispatcher_id, + sum(default_DOT_repair_order_details.price) default_DOT_total_repair_cost + FROM roads.repair_order_details AS default_DOT_repair_order_details LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders) + AS default_DOT_repair_order ON default_DOT_repair_order_details.repair_order_id = default_DOT_repair_order.repair_order_id + LEFT OUTER JOIN (SELECT default_DOT_dispatchers.company_name, + default_DOT_dispatchers.dispatcher_id + FROM roads.dispatchers AS default_DOT_dispatchers) + AS default_DOT_dispatcher ON default_DOT_repair_order.dispatcher_id = default_DOT_dispatcher.dispatcher_id + GROUP BY default_DOT_dispatcher.company_name, default_DOT_dispatcher.dispatcher_id + )SELECT m0_default_DOT_avg_repair_price.default_DOT_avg_repair_price, + m1_default_DOT_total_repair_cost.default_DOT_total_repair_cost, + COALESCE(m0_default_DOT_avg_repair_price.company_name, m1_default_DOT_total_repair_cost.company_name) company_name, + COALESCE(m0_default_DOT_avg_repair_price.dispatcher_id, m1_default_DOT_total_repair_cost.dispatcher_id) dispatcher_id + FROM m0_default_DOT_avg_repair_price FULL OUTER JOIN m1_default_DOT_total_repair_cost ON m0_default_DOT_avg_repair_price.company_name = m1_default_DOT_total_repair_cost.company_name AND m0_default_DOT_avg_repair_price.dispatcher_id = m1_default_DOT_total_repair_cost.dispatcher_id + """, + ) + + response = client_with_examples.get( + "/sql/", + params={ + "metrics": ["default.avg_repair_price", "default.total_repair_cost"], + "dimensions": [ + "default.hard_hat.hard_hat_id", + "default.hard_hat.first_name", + ], + "filters": [], + }, + ) + assert response.status_code == 200 + data = response.json() + assert compare_query_strings( + data["sql"], + """ + WITH + m0_default_DOT_avg_repair_price AS (SELECT default_DOT_hard_hat.first_name, + default_DOT_hard_hat.hard_hat_id, + avg(default_DOT_repair_order_details.price) AS default_DOT_avg_repair_price + FROM roads.repair_order_details AS default_DOT_repair_order_details LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders) + AS default_DOT_repair_order ON default_DOT_repair_order_details.repair_order_id = default_DOT_repair_order.repair_order_id + LEFT OUTER JOIN (SELECT default_DOT_hard_hats.first_name, + default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats) + AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + GROUP BY default_DOT_hard_hat.hard_hat_id, default_DOT_hard_hat.first_name + ), + m1_default_DOT_total_repair_cost AS (SELECT default_DOT_hard_hat.first_name, + default_DOT_hard_hat.hard_hat_id, + sum(default_DOT_repair_order_details.price) default_DOT_total_repair_cost + FROM roads.repair_order_details AS default_DOT_repair_order_details LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders) + AS default_DOT_repair_order ON default_DOT_repair_order_details.repair_order_id = default_DOT_repair_order.repair_order_id + LEFT OUTER JOIN (SELECT default_DOT_hard_hats.first_name, + default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats) + AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + GROUP BY default_DOT_hard_hat.hard_hat_id, default_DOT_hard_hat.first_name + )SELECT m0_default_DOT_avg_repair_price.default_DOT_avg_repair_price, + m1_default_DOT_total_repair_cost.default_DOT_total_repair_cost, + COALESCE(m0_default_DOT_avg_repair_price.first_name, m1_default_DOT_total_repair_cost.first_name) first_name, + COALESCE(m0_default_DOT_avg_repair_price.hard_hat_id, m1_default_DOT_total_repair_cost.hard_hat_id) hard_hat_id + FROM m0_default_DOT_avg_repair_price FULL OUTER JOIN m1_default_DOT_total_repair_cost ON m0_default_DOT_avg_repair_price.first_name = m1_default_DOT_total_repair_cost.first_name AND m0_default_DOT_avg_repair_price.hard_hat_id = m1_default_DOT_total_repair_cost.hard_hat_id + """, + ) + + +def test_get_sql_including_dimensions_with_disambiguated_columns( + client_with_examples: TestClient, +): + """ + Test getting SQL that includes dimensions with SQL that has to disambiguate projection columns with prefixes + """ + response = client_with_examples.get( + "/sql/", + params={ + "metrics": ["default.total_repair_cost"], + "dimensions": [ + "default.municipality_dim.state_id", + "default.municipality_dim.municipality_type_id", + "default.municipality_dim.municipality_type_desc", + "default.municipality_dim.municipality_id", + ], + "filters": [], + }, + ) + assert response.status_code == 200 + data = response.json() + assert compare_query_strings( + data["sql"], + """ + WITH + m0_default_DOT_total_repair_cost AS (SELECT default_DOT_municipality_dim.municipality_id, + default_DOT_municipality_dim.municipality_type_desc, + default_DOT_municipality_dim.municipality_type_id, + default_DOT_municipality_dim.state_id, + sum(default_DOT_repair_order_details.price) default_DOT_total_repair_cost + FROM roads.repair_order_details AS default_DOT_repair_order_details LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders) + AS default_DOT_repair_order ON default_DOT_repair_order_details.repair_order_id = default_DOT_repair_order.repair_order_id + LEFT OUTER JOIN (SELECT default_DOT_municipality.municipality_id AS municipality_id, + default_DOT_municipality_type.municipality_type_desc AS municipality_type_desc, + default_DOT_municipality_municipality_type.municipality_type_id AS municipality_type_id, + default_DOT_municipality.state_id + FROM roads.municipality AS default_DOT_municipality LEFT JOIN roads.municipality_municipality_type AS default_DOT_municipality_municipality_type ON default_DOT_municipality.municipality_id = default_DOT_municipality_municipality_type.municipality_id + LEFT JOIN roads.municipality_type AS default_DOT_municipality_type ON default_DOT_municipality_municipality_type.municipality_type_id = default_DOT_municipality_type.municipality_type_desc) + AS default_DOT_municipality_dim ON default_DOT_repair_order.municipality_id = default_DOT_municipality_dim.municipality_id + GROUP BY default_DOT_municipality_dim.state_id, default_DOT_municipality_dim.municipality_type_id, default_DOT_municipality_dim.municipality_type_desc, default_DOT_municipality_dim.municipality_id + )SELECT m0_default_DOT_total_repair_cost.default_DOT_total_repair_cost, + m0_default_DOT_total_repair_cost.municipality_id, + m0_default_DOT_total_repair_cost.municipality_type_desc, + m0_default_DOT_total_repair_cost.municipality_type_id, + m0_default_DOT_total_repair_cost.state_id + FROM m0_default_DOT_total_repair_cost + """, + ) + + response = client_with_examples.get( + "/sql/", + params={ + "metrics": ["default.avg_repair_price", "default.total_repair_cost"], + "dimensions": [ + "default.hard_hat.hard_hat_id", + "default.hard_hat.first_name", + ], + "filters": [], + }, + ) + assert response.status_code == 200 + data = response.json() + assert compare_query_strings( + data["sql"], + """ + WITH + m0_default_DOT_avg_repair_price AS (SELECT default_DOT_hard_hat.first_name, + default_DOT_hard_hat.hard_hat_id, + avg(default_DOT_repair_order_details.price) AS default_DOT_avg_repair_price + FROM roads.repair_order_details AS default_DOT_repair_order_details LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders) + AS default_DOT_repair_order ON default_DOT_repair_order_details.repair_order_id = default_DOT_repair_order.repair_order_id + LEFT OUTER JOIN (SELECT default_DOT_hard_hats.first_name, + default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats) + AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + GROUP BY default_DOT_hard_hat.hard_hat_id, default_DOT_hard_hat.first_name + ), + m1_default_DOT_total_repair_cost AS (SELECT default_DOT_hard_hat.first_name, + default_DOT_hard_hat.hard_hat_id, + sum(default_DOT_repair_order_details.price) default_DOT_total_repair_cost + FROM roads.repair_order_details AS default_DOT_repair_order_details LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders) + AS default_DOT_repair_order ON default_DOT_repair_order_details.repair_order_id = default_DOT_repair_order.repair_order_id + LEFT OUTER JOIN (SELECT default_DOT_hard_hats.first_name, + default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats) + AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + GROUP BY default_DOT_hard_hat.hard_hat_id, default_DOT_hard_hat.first_name + )SELECT m0_default_DOT_avg_repair_price.default_DOT_avg_repair_price, + m1_default_DOT_total_repair_cost.default_DOT_total_repair_cost, + COALESCE(m0_default_DOT_avg_repair_price.first_name, m1_default_DOT_total_repair_cost.first_name) first_name, + COALESCE(m0_default_DOT_avg_repair_price.hard_hat_id, m1_default_DOT_total_repair_cost.hard_hat_id) hard_hat_id + FROM m0_default_DOT_avg_repair_price FULL OUTER JOIN m1_default_DOT_total_repair_cost ON m0_default_DOT_avg_repair_price.first_name = m1_default_DOT_total_repair_cost.first_name AND m0_default_DOT_avg_repair_price.hard_hat_id = m1_default_DOT_total_repair_cost.hard_hat_id + """, + ) + + +def test_get_sql_for_metrics_filters_validate_dimensions( + client_with_examples: TestClient, +): + """ + Test that we extract the columns from filters to validate that they are from shared dimensions + """ + response = client_with_examples.get( + "/sql/", + params={ + "metrics": ["foo.bar.num_repair_orders", "foo.bar.avg_repair_price"], + "dimensions": [ + "foo.bar.hard_hat.country", + ], + "filters": ["default.hard_hat.city = 'Las Vegas'"], + "limit": 10, + }, + ) + data = response.json() + assert data["message"] == ( + "The filter `default.hard_hat.city = 'Las Vegas'` references the dimension " + "attribute `default.hard_hat.city`, which is not available on every metric and " + "thus cannot be included." + ) + + +def test_get_sql_for_metrics_orderby_not_in_dimensions( + client_with_examples: TestClient, +): + """ + Test that we extract the columns from filters to validate that they are from shared dimensions + """ + response = client_with_examples.get( + "/sql/", + params={ + "metrics": ["foo.bar.num_repair_orders", "foo.bar.avg_repair_price"], + "dimensions": [ + "foo.bar.hard_hat.country", + ], + "orderby": ["default.hard_hat.city"], + "limit": 10, + }, + ) + data = response.json() + assert data["message"] == ( + "Columns ['default.hard_hat.city'] in order by " + "clause must also be specified in the metrics or dimensions" + ) diff --git a/datajunction-server/tests/api/tags_test.py b/datajunction-server/tests/api/tags_test.py new file mode 100644 index 000000000..2bde445b8 --- /dev/null +++ b/datajunction-server/tests/api/tags_test.py @@ -0,0 +1,282 @@ +""" +Tests for tags. +""" +from unittest import mock + +from fastapi.testclient import TestClient + + +class TestTags: + """ + Test tags API endpoints. + """ + + def create_tag(self, client_with_examples: TestClient): + """ + Creates a tag. + """ + response = client_with_examples.post( + "/tags/", + json={ + "name": "sales_report", + "display_name": "Sales Report", + "description": "All metrics for sales", + "tag_type": "group", + "tag_metadata": {}, + }, + ) + return response + + def test_create_and_read_tag(self, client_with_examples: TestClient) -> None: + """ + Test ``POST /tags`` and ``GET /tags/{name}`` + """ + response = self.create_tag(client_with_examples) + expected_tag_output = { + "tag_metadata": {}, + "display_name": "Sales Report", + "id": 1, + "description": "All metrics for sales", + "name": "sales_report", + "tag_type": "group", + } + assert response.status_code == 201 + assert response.json() == expected_tag_output + + response = client_with_examples.get("/tags/sales_report/") + assert response.status_code == 200 + assert response.json() == expected_tag_output + + # Check history + response = client_with_examples.get("/history/tag/sales_report/") + assert response.json() == [ + { + "activity_type": "create", + "node": None, + "created_at": mock.ANY, + "details": {}, + "entity_name": "sales_report", + "entity_type": "tag", + "id": mock.ANY, + "post": {}, + "pre": {}, + "user": None, + }, + ] + + # Creating it again should raise an exception + response = self.create_tag(client_with_examples) + response_data = response.json() + assert ( + response_data["message"] == "A tag with name `sales_report` already exists!" + ) + + def test_update_tag(self, client_with_examples: TestClient) -> None: + """ + Tests updating a tag. + """ + response = self.create_tag(client_with_examples) + assert response.status_code == 201 + + # Trying updating the tag + response = client_with_examples.patch( + "/tags/sales_report/", + json={ + "description": "Helpful sales metrics", + "tag_metadata": {"order": 1}, + }, + ) + assert response.status_code == 200 + response_data = response.json() + assert response_data == { + "tag_metadata": {"order": 1}, + "display_name": "Sales Report", + "id": 1, + "description": "Helpful sales metrics", + "name": "sales_report", + "tag_type": "group", + } + + # Trying updating the tag + response = client_with_examples.patch( + "/tags/sales_report/", + json={}, + ) + assert response.json() == { + "tag_metadata": {"order": 1}, + "display_name": "Sales Report", + "id": 1, + "description": "Helpful sales metrics", + "name": "sales_report", + "tag_type": "group", + } + + # Check history + response = client_with_examples.get("/history/tag/sales_report/") + history = response.json() + assert [ + (activity["activity_type"], activity["entity_type"]) for activity in history + ] == [("create", "tag"), ("update", "tag"), ("update", "tag")] + + def test_list_tags(self, client_with_examples: TestClient) -> None: + """ + Test ``GET /tags`` + """ + response = self.create_tag(client_with_examples) + assert response.status_code == 201 + + response = client_with_examples.get("/tags/") + assert response.status_code == 200 + response_data = response.json() + + assert response_data == [ + { + "description": "All metrics for sales", + "display_name": "Sales Report", + "name": "sales_report", + "tag_metadata": {}, + "tag_type": "group", + }, + ] + + client_with_examples.post( + "/tags/", + json={ + "name": "impressions_report", + "display_name": "Impressions Report", + "description": "Metrics for various types of impressions", + "tag_type": "group", + "tag_metadata": {}, + }, + ) + + client_with_examples.post( + "/tags/", + json={ + "name": "rotors", + "display_name": "Rotors", + "description": "Department of brakes", + "tag_type": "business_area", + "tag_metadata": {}, + }, + ) + + response = client_with_examples.get("/tags/?tag_type=group") + assert response.status_code == 200 + response_data = response.json() + assert response_data == [ + { + "description": "All metrics for sales", + "tag_metadata": {}, + "name": "sales_report", + "display_name": "Sales Report", + "tag_type": "group", + }, + { + "description": "Metrics for various types of impressions", + "tag_metadata": {}, + "name": "impressions_report", + "display_name": "Impressions Report", + "tag_type": "group", + }, + ] + + response = client_with_examples.get("/tags/?tag_type=business_area") + assert response.status_code == 200 + response_data = response.json() + assert response_data == [ + { + "name": "rotors", + "display_name": "Rotors", + "description": "Department of brakes", + "tag_type": "business_area", + "tag_metadata": {}, + }, + ] + + def test_add_tag_to_node(self, client_with_examples: TestClient) -> None: + """ + Test ``POST /tags`` and ``GET /tags/{name}`` + """ + response = self.create_tag(client_with_examples) + assert response.status_code == 201 + + # Trying tag a node with a nonexistent tag should fail + response = client_with_examples.post( + "/nodes/default.items_sold_count/tag/?tag_name=random_tag", + ) + assert response.status_code == 404 + response_data = response.json() + assert ( + response_data["message"] == "A tag with name `random_tag` does not exist." + ) + + # Trying tag a node with an existing tag should succeed + response = client_with_examples.post( + "/nodes/default.items_sold_count/tag/?tag_name=sales_report", + ) + assert response.status_code == 201 + response_data = response.json() + assert response_data["message"] == ( + "Node `default.items_sold_count` has been " + "successfully tagged with tag `sales_report`" + ) + + # Test finding all nodes for that tag + response = client_with_examples.get( + "/tags/sales_report/nodes/", + ) + assert response.status_code == 200 + response_data = response.json() + assert len(response_data) == 1 + assert response_data == [ + "default.items_sold_count", + ] + + # Tag a second node + response = client_with_examples.post( + "/nodes/default.total_profit/tag/?tag_name=sales_report", + ) + assert response.status_code == 201 + response_data = response.json() + assert ( + response_data["message"] + == "Node `default.total_profit` has been successfully tagged with tag `sales_report`" + ) + + # Check history + response = client_with_examples.get("/history?node=default.total_profit") + history = response.json() + assert [ + (activity["activity_type"], activity["entity_type"]) for activity in history + ] == [("create", "node"), ("tag", "node")] + + # Check finding nodes for tag + response = client_with_examples.get( + "/tags/sales_report/nodes/", + ) + assert response.status_code == 200 + response_data = response.json() + assert len(response_data) == 2 + assert response_data == [ + "default.items_sold_count", + "default.total_profit", + ] + + # Check finding nodes for tag + response = client_with_examples.get( + "/tags/random_tag/nodes/", + ) + assert response.status_code == 404 + response_data = response.json() + assert ( + response_data["message"] == "A tag with name `random_tag` does not exist." + ) + + # Check finding nodes for tag + response = client_with_examples.get( + "/tags/sales_report/nodes/?node_type=transform", + ) + assert response.status_code == 200 + response_data = response.json() + assert response_data == [] diff --git a/datajunction-server/tests/conftest.py b/datajunction-server/tests/conftest.py new file mode 100644 index 000000000..d44e6c1cf --- /dev/null +++ b/datajunction-server/tests/conftest.py @@ -0,0 +1,326 @@ +""" +Fixtures for testing. +""" +# pylint: disable=redefined-outer-name, invalid-name, W0611 + +import re +from http.client import HTTPException +from typing import Collection, Iterator, List, Optional +from unittest.mock import MagicMock + +import pytest +from cachelib.simple import SimpleCache +from fastapi import Request +from fastapi.testclient import TestClient +from pytest_mock import MockerFixture +from requests.cookies import RequestsCookieJar +from sqlmodel import Session, SQLModel, create_engine +from sqlmodel.pool import StaticPool + +from datajunction_server.api.main import app +from datajunction_server.config import Settings +from datajunction_server.errors import DJQueryServiceClientException +from datajunction_server.internal.authentication.basic import parse_basic_auth_cookie +from datajunction_server.models import Column, Engine, User +from datajunction_server.models.materialization import ( + DruidMaterializationInput, + GenericMaterializationInput, + MaterializationInfo, +) +from datajunction_server.models.query import QueryCreate +from datajunction_server.models.user import OAuthProvider +from datajunction_server.service_clients import QueryServiceClient +from datajunction_server.utils import ( + get_query_service_client, + get_session, + get_settings, +) + +from .construction.fixtures import build_expectation, construction_session +from .examples import COLUMN_MAPPINGS, EXAMPLES, QUERY_DATA_MAPPINGS + + +@pytest.fixture +def settings(mocker: MockerFixture) -> Iterator[Settings]: + """ + Custom settings for unit tests. + """ + settings = Settings( + index="sqlite://", + repository="/path/to/repository", + results_backend=SimpleCache(default_timeout=0), + celery_broker=None, + redis_cache=None, + query_service=None, + secret="a-fake-secretkey", + ) + + mocker.patch( + "datajunction_server.utils.get_settings", + return_value=settings, + ) + + yield settings + + +@pytest.fixture +def session() -> Iterator[Session]: + """ + Create an in-memory SQLite session to test models. + """ + engine = create_engine( + "sqlite://", + connect_args={"check_same_thread": False}, + poolclass=StaticPool, + ) + SQLModel.metadata.create_all(engine) + with Session(engine, autoflush=False) as session: + yield session + + +@pytest.fixture +def query_service_client(mocker: MockerFixture) -> Iterator[QueryServiceClient]: + """ + Custom settings for unit tests. + """ + qs_client = QueryServiceClient(uri="query_service:8001") + + def mock_get_columns_for_table( + catalog: str, + schema: str, + table: str, + engine: Optional[Engine] = None, # pylint: disable=unused-argument + ) -> List[Column]: + return COLUMN_MAPPINGS[f"{catalog}.{schema}.{table}"] + + mocker.patch.object( + qs_client, + "get_columns_for_table", + mock_get_columns_for_table, + ) + + def mock_submit_query( + query_create: QueryCreate, + ) -> Collection[Collection[str]]: + return QUERY_DATA_MAPPINGS[ + query_create.submitted_query.strip() + .replace('"', "") + .replace("\n", "") + .replace(" ", "") + .replace("\t", "") + ] + + mocker.patch.object( + qs_client, + "submit_query", + mock_submit_query, + ) + + def mock_get_query( + query_id: str, + ) -> Collection[Collection[str]]: + if query_id == "foo-bar-baz": + raise DJQueryServiceClientException("Query foo-bar-baz not found.") + for _, response in QUERY_DATA_MAPPINGS.items(): + if response.id == query_id: + return response + raise RuntimeError(f"No mocked query exists for id {query_id}") + + mocker.patch.object( + qs_client, + "get_query", + mock_get_query, + ) + + mock_materialize = MagicMock() + mock_materialize.return_value = MaterializationInfo( + urls=["http://fake.url/job"], + output_tables=["common.a", "common.b"], + ) + mocker.patch.object( + qs_client, + "materialize", + mock_materialize, + ) + + mock_deactivate_materialization = MagicMock() + mock_deactivate_materialization.return_value = MaterializationInfo( + urls=["http://fake.url/job"], + output_tables=[], + ) + mocker.patch.object( + qs_client, + "deactivate_materialization", + mock_deactivate_materialization, + ) + + mock_get_materialization_info = MagicMock() + mock_get_materialization_info.return_value = MaterializationInfo( + urls=["http://fake.url/job"], + output_tables=["common.a", "common.b"], + ) + mocker.patch.object( + qs_client, + "get_materialization_info", + mock_get_materialization_info, + ) + yield qs_client + + +async def parse_basic_auth_cookie_override(request: Request): + """ + Override the auth cookie parser to inject a "dj" user + """ + request.state.user = User( + username="dj", + oauth_provider=OAuthProvider.BASIC, + ) + + +@pytest.fixture +def client( # pylint: disable=too-many-statements + session: Session, + settings: Settings, +) -> Iterator[TestClient]: + """ + Create a client for testing APIs. + """ + + def get_session_override() -> Session: + return session + + def get_settings_override() -> Settings: + return settings + + app.dependency_overrides[get_session] = get_session_override + app.dependency_overrides[get_settings] = get_settings_override + app.dependency_overrides[parse_basic_auth_cookie] = parse_basic_auth_cookie_override + with TestClient(app) as client: + yield client + + app.dependency_overrides.clear() + + +def post_and_raise_if_error(client: TestClient, endpoint: str, json: dict): + """ + Post the payload to the client and raise if there's an error + """ + response = client.post(endpoint, json=json) + if not response.ok: + raise HTTPException(response.text) + + +@pytest.fixture +def client_with_examples(client: TestClient) -> TestClient: + """ + load examples + """ + for endpoint, json in EXAMPLES: + post_and_raise_if_error(client=client, endpoint=endpoint, json=json) # type: ignore + return client + + +def compare_parse_trees(tree1, tree2): + """ + Recursively compare two ANTLR parse trees for equality. + """ + # Check if the node types are the same + if type(tree1) != type(tree2): # pylint: disable=unidiomatic-typecheck + return False + + # Check if the node texts are the same + if tree1.getText() != tree2.getText(): + return False + + # Check if the number of child nodes is the same + if tree1.getChildCount() != tree2.getChildCount(): + return False + + # Recursively compare child nodes + for i in range(tree1.getChildCount()): + child1 = tree1.getChild(i) + child2 = tree2.getChild(i) + if not compare_parse_trees(child1, child2): + return False + + # If all checks passed, the trees are equal + return True + + +COMMENT = re.compile(r"(--.*)|(/\*[\s\S]*?\*/)") +TRAILING_ZEROES = re.compile(r"(\d+\.\d*?[1-9])0+|\b(\d+)\.0+\b") +DIFF_IGNORE = re.compile(r"[\';\s]+") + + +def compare_query_strings(str1, str2): + """ + Recursively compare two ANTLR parse trees for equality, ignoring certain elements. + """ + + str1 = DIFF_IGNORE.sub("", TRAILING_ZEROES.sub("", COMMENT.sub("", str1))).upper() + str2 = DIFF_IGNORE.sub("", TRAILING_ZEROES.sub("", COMMENT.sub("", str2))).upper() + + return str1 == str2 + + +@pytest.fixture +def compare_query_strings_fixture(): + """ + Fixture for comparing two query strings. + """ + return compare_query_strings + + +@pytest.fixture +def client_with_query_service( # pylint: disable=too-many-statements + session: Session, + settings: Settings, + query_service_client: QueryServiceClient, +) -> TestClient: + """ + Add a mock query service to the test client. + """ + + def get_query_service_client_override() -> QueryServiceClient: + return query_service_client + + def get_session_override() -> Session: + return session + + def get_settings_override() -> Settings: + return settings + + app.dependency_overrides[get_session] = get_session_override + app.dependency_overrides[get_settings] = get_settings_override + app.dependency_overrides[ + get_query_service_client + ] = get_query_service_client_override + app.dependency_overrides[parse_basic_auth_cookie] = parse_basic_auth_cookie_override + + with TestClient(app) as client: + for endpoint, json in EXAMPLES: + post_and_raise_if_error(client=client, endpoint=endpoint, json=json) # type: ignore + yield client + + app.dependency_overrides.clear() + + +def pytest_addoption(parser): + """ + Add flags that enable groups of tests + """ + parser.addoption( + "--tpcds", + action="store_true", + dest="tpcds", + default=False, + help="include tests for parsing TPC-DS queries", + ) + + parser.addoption( + "--auth", + action="store_true", + dest="auth", + default=False, + help="Run authentication tests", + ) diff --git a/datajunction-server/tests/construction/__init__.py b/datajunction-server/tests/construction/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/datajunction-server/tests/construction/build_test.py b/datajunction-server/tests/construction/build_test.py new file mode 100644 index 000000000..48f59f196 --- /dev/null +++ b/datajunction-server/tests/construction/build_test.py @@ -0,0 +1,236 @@ +"""tests for building nodes""" + +from typing import Dict, Optional, Tuple + +import pytest +from sqlalchemy import select +from sqlmodel import Session + +import datajunction_server.sql.parsing.types as ct +from datajunction_server.construction.build import build_node +from datajunction_server.errors import DJException +from datajunction_server.models import Column, NodeRevision +from datajunction_server.models.node import Node, NodeType +from datajunction_server.utils import amenable_name + +from ..sql.utils import compare_query_strings +from .fixtures import BUILD_EXPECTATION_PARAMETERS + + +@pytest.mark.parametrize("node_name,db_id", BUILD_EXPECTATION_PARAMETERS) +@pytest.mark.asyncio +async def test_build_node(node_name: str, db_id: int, request): + """ + Test building a node + """ + construction_session: Session = request.getfixturevalue("construction_session") + build_expectation: Dict[ + str, + Dict[Optional[int], Tuple[bool, str]], + ] = request.getfixturevalue("build_expectation") + succeeds, expected = build_expectation[node_name][db_id] + node = next( + construction_session.exec( + select(Node).filter(Node.name == node_name), + ), + )[0] + + if succeeds: + ast = build_node( + construction_session, + node.current, + ) + assert compare_query_strings(str(ast), expected) + else: + with pytest.raises(Exception) as exc: + build_node( + construction_session, + node.current, + ) + assert expected in str(exc) + + +@pytest.mark.asyncio +async def test_build_metric_with_dimensions_aggs(request): + """ + Test building metric with dimensions + """ + construction_session: Session = request.getfixturevalue("construction_session") + num_comments_mtc: Node = next( + construction_session.exec( + select(Node).filter(Node.name == "basic.num_comments"), + ), + )[0] + query = build_node( + construction_session, + num_comments_mtc.current, + dimensions=["basic.dimension.users.country", "basic.dimension.users.gender"], + ) + expected = """ + SELECT + basic_DOT_dimension_DOT_users.country, + basic_DOT_dimension_DOT_users.gender, + COUNT(1) AS cnt + FROM basic.source.comments AS basic_DOT_source_DOT_comments + LEFT OUTER JOIN ( + SELECT + basic_DOT_source_DOT_users.country, + basic_DOT_source_DOT_users.gender, + basic_DOT_source_DOT_users.id + FROM basic.source.users AS basic_DOT_source_DOT_users + ) AS basic_DOT_dimension_DOT_users ON basic_DOT_source_DOT_comments.user_id = basic_DOT_dimension_DOT_users.id + GROUP BY + basic_DOT_dimension_DOT_users.country, basic_DOT_dimension_DOT_users.gender + """ + assert compare_query_strings(str(query), expected) + + +def test_build_metric_with_required_dimensions(request): + """ + Test building metric with bound dimensions + """ + construction_session: Session = request.getfixturevalue("construction_session") + num_comments_mtc: Node = next( + construction_session.exec( + select(Node).filter(Node.name == "basic.num_comments_bnd"), + ), + )[0] + query = build_node( + construction_session, + num_comments_mtc.current, + dimensions=["basic.dimension.users.country", "basic.dimension.users.gender"], + ) + expected = """ + SELECT + basic_DOT_dimension_DOT_users.country, + basic_DOT_dimension_DOT_users.gender, + COUNT(1) AS cnt, + basic_DOT_source_DOT_comments.id, + basic_DOT_source_DOT_comments.text + FROM basic.source.comments AS basic_DOT_source_DOT_comments + LEFT OUTER JOIN ( + SELECT + basic_DOT_source_DOT_users.country, + basic_DOT_source_DOT_users.gender, + basic_DOT_source_DOT_users.id + FROM basic.source.users AS basic_DOT_source_DOT_users + ) AS basic_DOT_dimension_DOT_users ON basic_DOT_source_DOT_comments.user_id = basic_DOT_dimension_DOT_users.id + GROUP BY + basic_DOT_source_DOT_comments.id, basic_DOT_source_DOT_comments.text, basic_DOT_dimension_DOT_users.country, basic_DOT_dimension_DOT_users.gender + """ + assert compare_query_strings(str(query), expected) + + +@pytest.mark.asyncio +async def test_raise_on_build_without_required_dimension_column(request): + """ + Test building a node that has a dimension reference without a column and no default `id` + """ + construction_session: Session = request.getfixturevalue("construction_session") + country_dim: Node = next( + construction_session.exec( + select(Node).filter(Node.name == "basic.dimension.countries"), + ), + )[0] + node_foo_ref = Node(name="foo", type=NodeType.TRANSFORM, current_version="1") + node_foo = NodeRevision( + name=node_foo_ref.name, + type=node_foo_ref.type, + node=node_foo_ref, + version="1", + query="""SELECT num_users FROM basic.transform.country_agg""", + columns=[ + Column( + name="num_users", + type=ct.IntegerType(), + dimension=country_dim, + ), + ], + ) + construction_session.add(node_foo) + construction_session.flush() + + node_bar_ref = Node(name="bar", type=NodeType.TRANSFORM, current_version="1") + node_bar = NodeRevision( + name=node_bar_ref.name, + type=node_bar_ref.type, + node=node_bar_ref, + version="1", + query="SELECT SUM(num_users) AS num_users " + "FROM foo GROUP BY basic.dimension.countries.country", + columns=[ + Column(name="num_users", type=ct.IntegerType()), + ], + ) + with pytest.raises(DJException) as exc_info: + build_node( + construction_session, + node_bar, + ) + + assert ( + "Node foo specifying dimension basic.dimension.countries on column " + "num_users does not specify a dimension column, but basic.dimension" + ".countries does not have the default key `id`." + ) in str(exc_info.value) + + +@pytest.mark.asyncio +async def test_build_metric_with_dimensions_filters(request): + """ + Test building metric with dimension filters + """ + construction_session: Session = request.getfixturevalue("construction_session") + num_comments_mtc: Node = next( + construction_session.exec( + select(Node).filter(Node.name == "basic.num_comments"), + ), + )[0] + query = build_node( + construction_session, + num_comments_mtc.current, + filters=["basic.dimension.users.age>=25", "basic.dimension.users.age<50"], + ) + expected = """ + SELECT + basic_DOT_dimension_DOT_users.age, + COUNT(1) AS cnt + FROM basic.source.comments AS basic_DOT_source_DOT_comments + LEFT OUTER JOIN ( + SELECT + basic_DOT_source_DOT_users.age, + basic_DOT_source_DOT_users.id + FROM basic.source.users AS basic_DOT_source_DOT_users + ) AS basic_DOT_dimension_DOT_users + ON basic_DOT_source_DOT_comments.user_id = basic_DOT_dimension_DOT_users.id + WHERE + basic_DOT_dimension_DOT_users.age >= 25 + AND basic_DOT_dimension_DOT_users.age < 50 + """ + assert compare_query_strings(str(query), expected) + + +@pytest.mark.asyncio +async def test_build_node_with_unnamed_column(request): + """ + Test building a node that has an unnamed column (so defaults to col) + """ + construction_session: Session = request.getfixturevalue("construction_session") + node_foo_ref = Node(name="foo", type=NodeType.TRANSFORM, current_version="1") + node_foo = NodeRevision( + node=node_foo_ref, + version="1", + query="""SELECT 1 FROM basic.dimension.countries""", + columns=[ + Column(name="col1", type=ct.IntegerType()), + ], + ) + build_node( + construction_session, + node_foo, + ) + + +def test_amenable_name(): + """testing for making an amenable name""" + assert amenable_name("hello.名") == "hello_DOT__UNK" diff --git a/datajunction-server/tests/construction/compile_test.py b/datajunction-server/tests/construction/compile_test.py new file mode 100644 index 000000000..cacb68740 --- /dev/null +++ b/datajunction-server/tests/construction/compile_test.py @@ -0,0 +1,204 @@ +""" +Tests for compiling nodes +""" + +# pylint: disable=too-many-lines +import pytest +from sqlmodel import Session + +from datajunction_server.errors import DJException +from datajunction_server.models import NodeRevision +from datajunction_server.models.node import Node +from datajunction_server.sql.parsing.ast import CompileContext +from datajunction_server.sql.parsing.backends.antlr4 import parse +from datajunction_server.sql.parsing.backends.exceptions import DJParseException + + +def test_get_table_node_is_none(construction_session: Session): + """ + Test a nonexistent table node with compound exception ignore + """ + + query = parse("select x from purchases") + ctx = CompileContext( + session=construction_session, + exception=DJException(), + ) + query.compile(ctx) + + assert "No node `purchases`" in str(ctx.exception.errors) + + +def test_missing_references(construction_session: Session): + """ + Test getting dependencies from a query that has dangling references + """ + query = parse("select a, b, c from does_not_exist") + exception = DJException() + context = CompileContext( + session=construction_session, + exception=exception, + ) + _, missing_references = query.extract_dependencies(context) + assert missing_references + + +def test_catching_dangling_refs_in_extract_dependencies(construction_session: Session): + """ + Test getting dependencies from a query that has dangling references when set not to raise + """ + query = parse("select a, b, c from does_not_exist") + exception = DJException() + context = CompileContext( + session=construction_session, + exception=exception, + ) + _, danglers = query.extract_dependencies(context) + assert "does_not_exist" in danglers + + +def test_raising_on_extract_from_node_with_no_query(): + """ + Test parsing an empty query fails + """ + with pytest.raises(DJParseException) as exc_info: + parse(None) + assert "Empty query provided!" in str(exc_info.value) + + +def test_raise_on_unnamed_subquery_in_implicit_join(construction_session: Session): + """ + Test raising on an unnamed subquery in an implicit join + """ + query = parse( + "SELECT country FROM basic.transform.country_agg, " + "(SELECT country FROM basic.transform.country_agg)", + ) + + context = CompileContext( + session=construction_session, + exception=DJException(), + ) + query.extract_dependencies(context) + assert ( + "Column `country` found in multiple tables. Consider using fully qualified name." + in str( + context.exception.errors, + ) + ) + + +def test_raise_on_ambiguous_column(construction_session: Session): + """ + Test raising on ambiguous column + """ + query = parse( + "SELECT country FROM basic.transform.country_agg a " + "LEFT JOIN basic.dimension.countries b on a.country = b.country", + ) + context = CompileContext( + session=construction_session, + exception=DJException(), + ) + query.compile(context) + assert ( + "Column `country` found in multiple tables. Consider using fully qualified name." + in str( + context.exception.errors, + ) + ) + + +def test_compile_node(construction_session: Session): + """ + Test compiling a node + """ + node_a = Node(name="A", current_version="1") + node_a_rev = NodeRevision( + node=node_a, + version="1", + query="SELECT country FROM basic.transform.country_agg", + ) + query_ast = parse(node_a_rev.query) + ctx = CompileContext(session=construction_session, exception=DJException()) + query_ast.compile(ctx) + + +def test_raise_on_compile_node_with_no_query(construction_session: Session): + """ + Test raising when compiling a node that has no query + """ + node_a = Node(name="A", current_version="1") + node_a_rev = NodeRevision(node=node_a, version="1") + + with pytest.raises(DJException) as exc_info: + query_ast = parse(node_a_rev.query) + ctx = CompileContext(session=construction_session, exception=DJException()) + query_ast.compile(ctx) + + assert "Empty query provided" in str(exc_info.value) + + +def test_raise_on_unjoinable_automatic_dimension_groupby(construction_session: Session): + """ + Test raising where a dimension node is automatically detected but unjoinable + """ + node_a = Node(name="A", current_version="1") + node_a_rev = NodeRevision( + node=node_a, + version="1", + query=( + "SELECT country FROM basic.transform.country_agg " + "GROUP BY basic.dimension.countries.country" + ), + ) + + query_ast = parse(node_a_rev.query) + ctx = CompileContext(session=construction_session, exception=DJException()) + query_ast.compile(ctx) + + assert ( + "Column `basic.dimension.countries.country` does not exist on any valid table." + in str( + ctx.exception.errors, + ) + ) + + +def test_raise_on_having_without_a_groupby(construction_session: Session): + """ + Test raising when using a having without a groupby + """ + node_a = Node(name="A", current_version="1") + node_a_rev = NodeRevision( + node=node_a, + version="1", + query=( + "SELECT country FROM basic.transform.country_agg " "HAVING country='US'" + ), + ) + + query_ast = parse(node_a_rev.query) + ctx = CompileContext(session=construction_session, exception=DJException()) + query_ast.compile(ctx) + + assert "HAVING without a GROUP BY is not allowed" in str(ctx.exception.errors) + + +def test_having(construction_session: Session): + """ + Test using having + """ + node_a = Node(name="A", current_version="1") + node_a_rev = NodeRevision( + node=node_a, + version="1", + query=( + "SELECT order_date, status FROM dbt.source.jaffle_shop.orders " + "GROUP BY dbt.dimension.customers.id " + "HAVING dbt.dimension.customers.id=1" + ), + ) + query_ast = parse(node_a_rev.query) + ctx = CompileContext(session=construction_session, exception=DJException()) + query_ast.compile(ctx) diff --git a/datajunction-server/tests/construction/exceptions_test.py b/datajunction-server/tests/construction/exceptions_test.py new file mode 100644 index 000000000..122da6aa8 --- /dev/null +++ b/datajunction-server/tests/construction/exceptions_test.py @@ -0,0 +1,49 @@ +""" +Tests for building nodes and extracting dependencies +""" +# pylint: disable=too-many-lines + +import pytest + +from datajunction_server.construction.exceptions import CompoundBuildException +from datajunction_server.errors import DJError, DJException, ErrorCode + + +def test_compound_build_exception(): + """ + Test raising a CompoundBuildException + """ + CompoundBuildException().reset() + CompoundBuildException().set_raise(False) # pylint: disable=protected-access + CompoundBuildException().append( + error=DJError( + code=ErrorCode.INVALID_SQL_QUERY, + message="This SQL is invalid.", + ), + message="Testing a compound build exception", + ) + + assert len(CompoundBuildException().errors) == 1 + assert CompoundBuildException().errors[0].code == ErrorCode.INVALID_SQL_QUERY + + assert "Found 1 issue" in str(CompoundBuildException()) + + CompoundBuildException().reset() + + +def test_raise_compound_build_exception(): + """ + Test raising a CompoundBuildException + """ + CompoundBuildException().reset() + CompoundBuildException().set_raise(True) # pylint: disable=protected-access + with pytest.raises(DJException) as exc_info: + CompoundBuildException().append( + error=DJError( + code=ErrorCode.INVALID_SQL_QUERY, + message="This SQL is invalid.", + ), + message="Testing a compound build exception", + ) + + assert "Testing a compound build exception" in str(exc_info.value) diff --git a/datajunction-server/tests/construction/fixtures.py b/datajunction-server/tests/construction/fixtures.py new file mode 100644 index 000000000..6286eb642 --- /dev/null +++ b/datajunction-server/tests/construction/fixtures.py @@ -0,0 +1,650 @@ +"""fixtures for testing construction""" +# noqa: W191,E101 +# pylint: disable=line-too-long + +from typing import Dict, List, Optional, Tuple + +import pytest +from sqlmodel import Session + +from datajunction_server.models import ( + AttributeType, + Column, + ColumnAttribute, + Database, + NodeRevision, + Table, +) +from datajunction_server.models.node import Node, NodeType +from datajunction_server.sql.parsing.types import ( + DateType, + FloatType, + IntegerType, + MapType, + StringType, + TimestampType, +) + +BUILD_NODE_NAMES: List[str] = [ + "basic.source.users", + "basic.source.comments", + "basic.dimension.users", + "dbt.source.jaffle_shop.orders", + "dbt.dimension.customers", + "dbt.source.jaffle_shop.customers", + "basic.dimension.countries", + "basic.transform.country_agg", + "basic.num_comments", + "basic.num_users", + "dbt.transform.customer_agg", +] + +BUILD_EXPECTATION_PARAMETERS: List[Tuple[str, Optional[int]]] = list( + zip(BUILD_NODE_NAMES * 3, [None] * len(BUILD_NODE_NAMES)), +) + + +@pytest.fixture +def build_expectation() -> Dict[str, Dict[Optional[int], Tuple[bool, str]]]: + """map node names with database ids to what their build results should be""" + return { + """basic.source.users""": { + None: ( + True, + """ + SELECT * FROM basic.source.users + """, + ), + }, + """basic.source.comments""": { + None: ( + True, + """ + SELECT * FROM basic.source.comments + """, + ), + }, + """basic.dimension.users""": { + None: ( + True, + """SELECT basic_DOT_source_DOT_users.id, + basic_DOT_source_DOT_users.full_name, + basic_DOT_source_DOT_users.age, + basic_DOT_source_DOT_users.country, + basic_DOT_source_DOT_users.gender, + basic_DOT_source_DOT_users.preferred_language, + basic_DOT_source_DOT_users.secret_number + FROM basic.source.users AS basic_DOT_source_DOT_users""", + ), + }, + """dbt.source.jaffle_shop.orders""": { + None: ( + False, + """Node has no query. Cannot generate a build plan without a query.""", + ), + }, + """dbt.dimension.customers""": { + None: ( + True, + """SELECT dbt_DOT_source_DOT_jaffle_shop_DOT_customers.id, + dbt_DOT_source_DOT_jaffle_shop_DOT_customers.first_name, + dbt_DOT_source_DOT_jaffle_shop_DOT_customers.last_name + FROM dbt.source.jaffle_shop.customers AS dbt_DOT_source_DOT_jaffle_shop_DOT_customers""", + ), + }, + """dbt.source.jaffle_shop.customers""": { + None: ( + False, + """Node has no query. Cannot generate a build plan without a query.""", + ), + }, + """basic.dimension.countries""": { + None: ( + True, + """SELECT basic_DOT_dimension_DOT_users.country, + COUNT(1) AS user_cnt + FROM (SELECT basic_DOT_source_DOT_users.age, + basic_DOT_source_DOT_users.country, + basic_DOT_source_DOT_users.full_name, + basic_DOT_source_DOT_users.gender, + basic_DOT_source_DOT_users.id, + basic_DOT_source_DOT_users.preferred_language, + basic_DOT_source_DOT_users.secret_number + FROM basic.source.users AS basic_DOT_source_DOT_users + +) AS basic_DOT_dimension_DOT_users + + GROUP BY basic_DOT_dimension_DOT_users.country""", + ), + }, + """basic.transform.country_agg""": { + None: ( + True, + """SELECT basic_DOT_source_DOT_users.country, + COUNT(DISTINCT basic_DOT_source_DOT_users.id) AS num_users + FROM basic.source.users AS basic_DOT_source_DOT_users + + GROUP BY basic_DOT_source_DOT_users.country""", + ), + }, + """basic.num_comments""": { + None: ( + True, + """SELECT COUNT(1) AS cnt + FROM basic.source.comments AS basic_DOT_source_DOT_comments""", + ), + }, + """basic.num_users""": { + None: ( + True, + """SELECT SUM(basic_DOT_transform_DOT_country_agg.num_users) AS col0 + FROM (SELECT basic_DOT_source_DOT_users.country, + COUNT(DISTINCT basic_DOT_source_DOT_users.id) AS num_users + FROM basic.source.users AS basic_DOT_source_DOT_users + + GROUP BY basic_DOT_source_DOT_users.country) AS basic_DOT_transform_DOT_country_agg""", + ), + }, + """dbt.transform.customer_agg""": { + None: ( + True, + """SELECT dbt_DOT_source_DOT_jaffle_shop_DOT_customers.first_name, + dbt_DOT_source_DOT_jaffle_shop_DOT_customers.id, + dbt_DOT_source_DOT_jaffle_shop_DOT_customers.last_name, + COUNT(1) AS order_cnt + FROM dbt.source.jaffle_shop.orders AS dbt_DOT_source_DOT_jaffle_shop_DOT_orders JOIN dbt.source.jaffle_shop.customers AS dbt_DOT_source_DOT_jaffle_shop_DOT_customers ON dbt_DOT_source_DOT_jaffle_shop_DOT_orders.user_id = dbt_DOT_source_DOT_jaffle_shop_DOT_customers.id + GROUP BY dbt_DOT_source_DOT_jaffle_shop_DOT_customers.id, dbt_DOT_source_DOT_jaffle_shop_DOT_customers.first_name, dbt_DOT_source_DOT_jaffle_shop_DOT_customers.last_name""", + ), + }, + } + + +@pytest.fixture +def construction_session( # pylint: disable=too-many-locals + session: Session, +) -> Session: + """ + Add some source nodes and transform nodes to facilitate testing of extracting dependencies + """ + + postgres = Database(name="postgres", URI="", cost=10, id=1) + + gsheets = Database(name="gsheets", URI="", cost=100, id=2) + primary_key = AttributeType(namespace="system", name="primary_key", description="") + countries_dim_ref = Node( + name="basic.dimension.countries", + type=NodeType.DIMENSION, + current_version="1", + ) + countries_dim = NodeRevision( + name=countries_dim_ref.name, + type=countries_dim_ref.type, + node=countries_dim_ref, + version="1", + query=""" + SELECT country, + COUNT(1) AS user_cnt + FROM basic.dimension.users + GROUP BY country + """, + columns=[ + Column( + name="country", + type=StringType(), + attributes=[ColumnAttribute(attribute_type=primary_key)], + ), + Column(name="user_cnt", type=IntegerType()), + ], + ) + + user_dim_ref = Node( + name="basic.dimension.users", + type=NodeType.DIMENSION, + current_version="1", + ) + user_dim = NodeRevision( + name=user_dim_ref.name, + type=user_dim_ref.type, + node=user_dim_ref, + version="1", + query=""" + SELECT id, + full_name, + age, + country, + gender, + preferred_language, + secret_number + FROM basic.source.users + """, + columns=[ + Column( + name="id", + type=IntegerType(), + attributes=[ColumnAttribute(attribute_type=primary_key)], + ), + Column(name="full_name", type=StringType()), + Column(name="age", type=IntegerType()), + Column(name="country", type=StringType()), + Column(name="gender", type=StringType()), + Column(name="preferred_language", type=StringType()), + Column(name="secret_number", type=FloatType()), + ], + ) + + country_agg_tfm_ref = Node( + name="basic.transform.country_agg", + type=NodeType.TRANSFORM, + current_version="1", + ) + country_agg_tfm = NodeRevision( + name=country_agg_tfm_ref.name, + type=country_agg_tfm_ref.type, + node=country_agg_tfm_ref, + version="1", + query=""" + SELECT country, + COUNT(DISTINCT id) AS num_users + FROM basic.source.users + GROUP BY country + """, + columns=[ + Column( + name="country", + type=StringType(), + dimension=user_dim_ref, + dimension_column="country", + ), + Column(name="num_users", type=IntegerType()), + ], + ) + + users_src_ref = Node( + name="basic.source.users", + type=NodeType.SOURCE, + current_version="1", + ) + users_src = NodeRevision( + name=users_src_ref.name, + type=users_src_ref.type, + node=users_src_ref, + version="1", + columns=[ + Column(name="id", type=IntegerType()), + Column(name="full_name", type=StringType()), + Column( + name="names_map", + type=MapType(key_type=StringType(), value_type=StringType()), + ), + Column( + name="user_metadata", + type=MapType( + key_type=StringType(), + value_type=MapType( + key_type=StringType(), + value_type=MapType( + key_type=StringType(), + value_type=FloatType(), + ), + ), + ), + ), + Column(name="age", type=IntegerType()), + Column(name="country", type=StringType()), + Column(name="gender", type=StringType()), + Column(name="preferred_language", type=StringType()), + Column(name="secret_number", type=FloatType()), + ], + tables=[ + Table( + node_id=4249, + schema="basic", + table="comments", + columns=[ + Column(name="id", type=IntegerType()), + Column(name="full_name", type=StringType()), + Column( + name="names_map", + type=MapType(key_type=StringType(), value_type=StringType()), + ), + Column( + name="user_metadata", + type=MapType( + key_type=StringType(), + value_type=MapType( + key_type=StringType(), + value_type=MapType( + key_type=StringType(), + value_type=FloatType(), + ), + ), + ), + ), + Column(name="age", type=IntegerType()), + Column(name="country", type=StringType()), + Column(name="gender", type=StringType()), + Column(name="preferred_language", type=StringType()), + Column(name="secret_number", type=FloatType()), + ], + cost=10.0, + database=postgres, + database_id=1, + ), + Table( + node_id=4250, + table="comments", + columns=[ + Column(name="id", type=IntegerType()), + Column(name="full_name", type=StringType()), + Column( + name="names_map", + type=MapType(key_type=StringType(), value_type=StringType()), + ), + Column( + name="user_metadata", + type=MapType( + key_type=StringType(), + value_type=MapType( + key_type=StringType(), + value_type=MapType( + key_type=StringType(), + value_type=FloatType(), + ), + ), + ), + ), + Column(name="age", type=IntegerType()), + Column(name="country", type=StringType()), + Column(name="gender", type=StringType()), + Column(name="preferred_language", type=StringType()), + Column(name="secret_number", type=FloatType()), + ], + cost=100.0, + database=gsheets, + database_id=2, + ), + ], + ) + + comments_src_ref = Node( + name="basic.source.comments", + type=NodeType.SOURCE, + current_version="1", + ) + comments_src = NodeRevision( + name=comments_src_ref.name, + type=comments_src_ref.type, + node=comments_src_ref, + version="1", + columns=[ + Column(name="id", type=IntegerType()), + Column( + name="user_id", + type=IntegerType(), + dimension=user_dim_ref, + ), + Column(name="timestamp", type=TimestampType()), + Column(name="text", type=StringType()), + ], + tables=[ + Table( + node_id=4251, + schema="basic", + table="comments", + columns=[ + Column(name="id", type=IntegerType()), + Column(name="user_id", type=IntegerType()), + Column(name="timestamp", type=TimestampType()), + Column(name="text", type=StringType()), + ], + cost=10.0, + database=postgres, + database_id=1, + ), + Table( + node_id=4252, + table="comments", + columns=[ + Column(name="id", type=IntegerType()), + Column(name="user_id", type=IntegerType()), + Column(name="timestamp", type=TimestampType()), + Column(name="text", type=StringType()), + ], + cost=100.0, + database=gsheets, + database_id=2, + ), + ], + ) + + num_comments_mtc_ref = Node( + name="basic.num_comments", + type=NodeType.METRIC, + current_version="1", + ) + num_comments_mtc = NodeRevision( + name=num_comments_mtc_ref.name, + type=num_comments_mtc_ref.type, + node=num_comments_mtc_ref, + version="1", + query=""" + SELECT COUNT(1) AS cnt + FROM basic.source.comments + """, + columns=[ + Column(name="cnt", type=IntegerType()), + ], + ) + + num_comments_mtc_bnd_dims_ref = Node( + name="basic.num_comments_bnd", + type=NodeType.METRIC, + current_version="1", + ) + num_comments_mtc_bnd_dims = NodeRevision( + name=num_comments_mtc_bnd_dims_ref.name, + type=num_comments_mtc_bnd_dims_ref.type, + node=num_comments_mtc_bnd_dims_ref, + version="1", + query=""" + SELECT COUNT(1) AS cnt + FROM basic.source.comments + """, + columns=[ + Column(name="cnt", type=IntegerType()), + ], + required_dimensions=[ + comments_src.columns[0], # pylint: disable=E1136 + comments_src.columns[-1], # pylint: disable=E1136 + ], + ) + + num_users_mtc_ref = Node( + name="basic.num_users", + type=NodeType.METRIC, + current_version="1", + ) + num_users_mtc = NodeRevision( + name=num_users_mtc_ref.name, + type=num_users_mtc_ref.type, + node=num_users_mtc_ref, + version="1", + query=""" + SELECT SUM(num_users) AS col0 + FROM basic.transform.country_agg + """, + columns=[ + Column(name="col0", type=IntegerType()), + ], + ) + num_users_us_join_mtc_ref = Node( + name="basic.num_users_us", + type=NodeType.METRIC, + current_version="1", + ) + num_users_us_join_mtc = NodeRevision( + name=num_users_us_join_mtc_ref.name, + type=num_users_us_join_mtc_ref.type, + node=num_users_us_join_mtc_ref, + version="1", + query=""" + SELECT SUM(a.num_users) as sum_users + FROM basic.transform.country_agg a + INNER JOIN basic.source.users b + ON a.country=b.country + WHERE a.country='US' + """, + columns=[ + Column( + name="sum_users", + type=IntegerType(), + ), + ], + ) + customers_dim_ref = Node( + name="dbt.dimension.customers", + type=NodeType.DIMENSION, + current_version="1", + ) + customers_dim = NodeRevision( + name=customers_dim_ref.name, + type=customers_dim_ref.type, + node=customers_dim_ref, + version="1", + query=""" + SELECT id, + first_name, + last_name + FROM dbt.source.jaffle_shop.customers + """, + columns=[ + Column( + name="id", + type=IntegerType(), + attributes=[ColumnAttribute(attribute_type=primary_key)], + ), + Column(name="first_name", type=StringType()), + Column(name="last_name", type=StringType()), + ], + ) + + customers_agg_tfm_ref = Node( + name="dbt.transform.customer_agg", + type=NodeType.TRANSFORM, + current_version="1", + ) + customers_agg_tfm = NodeRevision( + name=customers_agg_tfm_ref.name, + type=customers_agg_tfm_ref.type, + node=customers_agg_tfm_ref, + version="1", + query=""" + SELECT c.id, + c.first_name, + c.last_name, + COUNT(1) AS order_cnt + FROM dbt.source.jaffle_shop.orders o + JOIN dbt.source.jaffle_shop.customers c ON o.user_id = c.id + GROUP BY c.id, + c.first_name, + c.last_name + """, + columns=[ + Column(name="id", type=IntegerType()), + Column(name="first_name", type=StringType()), + Column(name="last_name", type=StringType()), + Column(name="order_cnt", type=IntegerType()), + ], + ) + + orders_src_ref = Node( + name="dbt.source.jaffle_shop.orders", + type=NodeType.SOURCE, + current_version="1", + ) + orders_src = NodeRevision( + name=orders_src_ref.name, + type=orders_src_ref.type, + node=orders_src_ref, + version="1", + columns=[ + Column(name="id", type=IntegerType()), + Column( + name="user_id", + type=IntegerType(), + dimension=customers_dim_ref, + dimension_column="event_id", + ), + Column(name="order_date", type=DateType()), + Column(name="status", type=StringType()), + Column(name="_etl_loaded_at", type=TimestampType()), + ], + tables=[ + Table( + node_id=4253, + schema="jaffle_shop", + table="orders", + columns=[ + Column(name="id", type=IntegerType()), + Column(name="user_id", type=IntegerType()), + Column(name="order_date", type=DateType()), + Column(name="status", type=StringType()), + Column(name="_etl_loaded_at", type=TimestampType()), + ], + cost=10.0, + database=postgres, + database_id=1, + ), + ], + ) + + customers_src_ref = Node( + name="dbt.source.jaffle_shop.customers", + type=NodeType.SOURCE, + current_version="1", + ) + customers_src = NodeRevision( + name=customers_src_ref.name, + type=customers_src_ref.type, + node=customers_src_ref, + version="1", + columns=[ + Column(name="id", type=IntegerType()), + Column(name="first_name", type=StringType()), + Column(name="last_name", type=StringType()), + ], + tables=[ + Table( + node_id=4254, + schema="jaffle_shop", + table="customers", + columns=[ + Column(name="id", type=IntegerType()), + Column(name="first_name", type=StringType()), + Column(name="last_name", type=StringType()), + ], + cost=10.0, + database=postgres, + database_id=1, + ), + ], + ) + + session.add(postgres) + session.add(gsheets) + session.add(countries_dim) + session.add(user_dim) + session.add(country_agg_tfm) + session.add(users_src) + session.add(comments_src) + session.add(num_users_us_join_mtc) + session.add(num_comments_mtc) + session.add(num_comments_mtc_bnd_dims) + session.add(num_users_mtc) + session.add(customers_dim) + session.add(customers_agg_tfm) + session.add(orders_src) + session.add(customers_src) + + session.commit() + return session diff --git a/datajunction-server/tests/construction/inference_test.py b/datajunction-server/tests/construction/inference_test.py new file mode 100644 index 000000000..04d96fdeb --- /dev/null +++ b/datajunction-server/tests/construction/inference_test.py @@ -0,0 +1,671 @@ +"""Test type inference.""" + +# pylint: disable=W0621,C0325 +import pytest +from sqlmodel import Session + +from datajunction_server.errors import DJException +from datajunction_server.sql.parsing import ast +from datajunction_server.sql.parsing.ast import CompileContext +from datajunction_server.sql.parsing.backends.antlr4 import parse +from datajunction_server.sql.parsing.backends.exceptions import DJParseException +from datajunction_server.sql.parsing.types import ( + BigIntType, + BooleanType, + ColumnType, + DateType, + DayTimeIntervalType, + DecimalType, + DoubleType, + FixedType, + FloatType, + IntegerType, + ListType, + MapType, + NullType, + StringType, + TimestampType, + TimeType, +) + + +def test_infer_column_with_table(construction_session: Session): + """ + Test getting the type of a column that has a table + """ + table = ast.Table( + ast.Name("orders", namespace=ast.Name("dbt.source.jaffle_shop")), + ) + ctx = CompileContext( + session=construction_session, + exception=DJException(), + ) + table.compile(ctx) + assert table.columns[0].type == IntegerType() + assert table.columns[1].type == IntegerType() + assert table.columns[2].type == DateType() + assert table.columns[3].type == StringType() + + +def test_infer_values(): + """ + Test inferring types from values directly + """ + assert ast.String(value="foo").type == StringType() + assert ast.Number(value=10).type == IntegerType() + assert ast.Number(value=-10).type == IntegerType() + assert ast.Number(value=922337203685477).type == BigIntType() + assert ast.Number(value=-922337203685477).type == BigIntType() + assert ast.Number(value=3.4e39).type == DoubleType() + assert ast.Number(value=-3.4e39).type == DoubleType() + assert ast.Number(value=3.4e38).type == FloatType() + assert ast.Number(value=-3.4e38).type == FloatType() + + +def test_raise_on_invalid_infer_binary_op(): + """ + Test raising when trying to infer types from an invalid binary op + """ + with pytest.raises(DJParseException) as exc_info: + ast.BinaryOp( # pylint: disable=expression-not-assigned + op=ast.BinaryOpKind.Modulo, + left=ast.String(value="foo"), + right=ast.String(value="bar"), + ).type + + assert ( + "Incompatible types in binary operation foo % bar. " + "Got left string, right string." + ) in str(exc_info.value) + + +def test_infer_column_with_an_aliased_table(construction_session: Session): + """ + Test getting the type of a column that has an aliased table + """ + ctx = CompileContext( + session=construction_session, + exception=DJException(), + ) + table = ast.Table( + ast.Name("orders", namespace=ast.Name("dbt.source.jaffle_shop")), + ) + alias = ast.Alias( + alias=ast.Name( + name="foo", + namespace=ast.Name( + name="a", + namespace=ast.Name( + name="b", + namespace=ast.Name("c"), + ), + ), + ), + child=table, + ) + alias.compile(ctx) + + assert alias.child.columns[0].type == IntegerType() + assert alias.child.columns[1].type == IntegerType() + assert alias.child.columns[2].type == DateType() + assert alias.child.columns[3].type == StringType() + assert alias.child.columns[4].type == TimestampType() + + +def test_raising_when_table_has_no_dj_node(): + """ + Test raising when getting the type of a column that has a table with no DJ node + """ + table = ast.Table(ast.Name("orders")) + col = ast.Column(ast.Name("status"), _table=table) + + with pytest.raises(DJParseException) as exc_info: + col.type # pylint: disable=pointless-statement + + assert ("Cannot resolve type of column orders.status") in str(exc_info.value) + + +def test_raising_when_select_has_multiple_expressions_in_projection(): + """ + Test raising when a select has more than one in projection + """ + with pytest.raises(DJParseException) as exc_info: + parse("select 1, 2").select.type # pylint: disable=expression-not-assigned + + assert ("single expression in its projection") in str(exc_info.value) + + +def test_raising_when_between_different_types(): + """ + Test raising when a between has multiple types + """ + with pytest.raises(DJParseException) as exc_info: + parse( # pylint: disable=expression-not-assigned + "select 1 between 'hello' and TRUE", + ).select.type + + assert ("BETWEEN expects all elements to have the same type") in str(exc_info.value) + + +def test_raising_when_unop_bad_type(): + """ + Test raising when a unop gets a bad type + """ + with pytest.raises(DJParseException) as exc_info: + parse( # pylint: disable=expression-not-assigned + "select not 'hello'", + ).select.type + + assert ("Incompatible type in unary operation") in str(exc_info.value) + + +def test_raising_when_expression_has_no_parent(): + """ + Test raising when getting the type of a column that has no parent + """ + col = ast.Column(ast.Name("status"), _table=None) + + with pytest.raises(DJParseException) as exc_info: + col.type # pylint: disable=pointless-statement + + assert "Cannot resolve type of column status that has no parent" in str( + exc_info.value, + ) + + +def test_infer_map_subscripts(construction_session: Session): + """ + Test inferring map subscript types + """ + query = parse( + """ + SELECT + names_map["first"] as first_name, + names_map["last"] as last_name, + user_metadata["propensity_score"] as propensity_score, + user_metadata["propensity_score"]["weighted"] as weighted_propensity_score, + user_metadata["propensity_score"]["weighted"]["year"] as weighted_propensity_score_year + FROM basic.source.users + """, + ) + exc = DJException() + ctx = CompileContext(session=construction_session, exception=exc) + query.compile(ctx) + types = [ + StringType(), + StringType(), + MapType( + key_type=StringType(), + value_type=MapType(key_type=StringType(), value_type=FloatType()), + ), + MapType(key_type=StringType(), value_type=FloatType()), + FloatType(), + ] + assert types == [exp.type for exp in query.select.projection] # type: ignore + + +def test_infer_types_complicated(construction_session: Session): + """ + Test inferring complicated types + """ + query = parse( + """ + SELECT id+1-2/3*5%6&10|8^5, + CAST('2022-01-01T12:34:56Z' AS TIMESTAMP), + -- Raw('average({id})', 'INT', True), + -- Raw('aggregate(array(1, 2, {id}), 0, (acc, x) -> acc + x, acc -> acc * 10)', 'INT'), + -- Raw('NOW()', 'datetime'), + -- DATE_TRUNC('day', '2014-03-10'), + NOW(), + Coalesce(NULL, 5), + Coalesce(NULL), + NULL, + MAX(id) OVER + (PARTITION BY first_name ORDER BY last_name) + AS running_total, + MAX(id) OVER + (PARTITION BY first_name ORDER BY last_name) + AS running_total, + MIN(id) OVER + (PARTITION BY first_name ORDER BY last_name) + AS running_total, + AVG(id) OVER + (PARTITION BY first_name ORDER BY last_name) + AS running_total, + COUNT(id) OVER + (PARTITION BY first_name ORDER BY last_name) + AS running_total, + SUM(id) OVER + (PARTITION BY first_name ORDER BY last_name) + AS running_total, + NOT TRUE, + 10, + id>5, + id<5, + id>=5, + id<=5, + id BETWEEN 4 AND 5, + id IN (5, 5), + id NOT IN (3, 4), + id NOT IN (SELECT -5), + first_name LIKE 'Ca%', + id is null, + (id=5)=TRUE, + 'hello world', + first_name as fn, + last_name<>'yoyo' and last_name='yoyo' or last_name='yoyo', + last_name, + bizarre, + (select 5.0), + CASE WHEN first_name = last_name THEN COUNT(DISTINCT first_name) ELSE + COUNT(DISTINCT last_name) END + FROM ( + SELECT id, + first_name, + last_name<>'yoyo' and last_name='yoyo' or last_name='yoyo' as bizarre, + last_name + FROM dbt.source.jaffle_shop.customers + ) + """, + ) + exc = DJException() + ctx = CompileContext(session=construction_session, exception=exc) + query.compile(ctx) + types = [ + IntegerType(), + TimestampType(), + TimestampType(), + IntegerType(), + NullType(), + NullType(), + IntegerType(), + IntegerType(), + IntegerType(), + DoubleType(), + BigIntType(), + BigIntType(), + BooleanType(), + IntegerType(), + BooleanType(), + BooleanType(), + BooleanType(), + BooleanType(), + BooleanType(), + BooleanType(), + BooleanType(), + BooleanType(), + BooleanType(), + BooleanType(), + BooleanType(), + StringType(), + StringType(), + BooleanType(), + StringType(), + BooleanType(), + FloatType(), + BigIntType(), + ] + assert types == [exp.type for exp in query.select.projection] # type: ignore + + +def test_infer_bad_case_types(construction_session: Session): + """ + Test inferring mismatched case types. + """ + with pytest.raises(Exception) as excinfo: + query = parse( + """ + SELECT + CASE WHEN first_name = last_name THEN COUNT(DISTINCT first_name) ELSE last_name END + FROM dbt.source.jaffle_shop.customers + """, + ) + ctx = CompileContext( + session=construction_session, + exception=DJException(), + ) + query.compile(ctx) + [ # pylint: disable=pointless-statement + exp.type for exp in query.select.projection # type: ignore + ] + + assert str(excinfo.value) == "Not all the same type in CASE! Found: bigint, string" + + +def test_infer_types_avg(construction_session: Session): + """ + Test type inference of functions + """ + + query = parse( + """ + SELECT + AVG(id) OVER + (PARTITION BY first_name ORDER BY last_name), + AVG(CAST(id AS DECIMAL(8, 6))), + AVG(CAST(id AS INTERVAL DAY TO SECOND)), + STDDEV(id), + stddev_samp(id), + stddev_pop(id), + variance(id), + var_pop(id) + FROM dbt.source.jaffle_shop.customers + """, + ) + exc = DJException() + ctx = CompileContext(session=construction_session, exception=exc) + query.compile(ctx) + types = [ + DoubleType(), + DecimalType(12, 10), + DayTimeIntervalType(), + DoubleType(), + DoubleType(), + DoubleType(), + DoubleType(), + DoubleType(), + ] + assert types == [exp.type for exp in query.select.projection] # type: ignore + + +def test_infer_types_min_max_sum_ceil(construction_session: Session): + """ + Test type inference of functions + """ + + query = parse( + """ + SELECT + MIN(id) OVER + (PARTITION BY first_name ORDER BY last_name), + MAX(id) OVER + (PARTITION BY first_name ORDER BY last_name), + SUM(id) OVER + (PARTITION BY first_name ORDER BY last_name), + CEIL(id), + PERCENT_RANK(id) OVER (PARTITION BY id ORDER BY id) + FROM dbt.source.jaffle_shop.customers + """, + ) + exc = DJException() + ctx = CompileContext(session=construction_session, exception=exc) + query.compile(ctx) + types = [IntegerType(), IntegerType(), BigIntType(), BigIntType(), DoubleType()] + assert types == [exp.type for exp in query.select.projection] # type: ignore + + +def test_infer_types_count(construction_session: Session): + """ + Test type inference of functions + """ + + query = parse( + """ + SELECT + COUNT(id) OVER + (PARTITION BY first_name ORDER BY last_name), + COUNT(DISTINCT last_name) + FROM dbt.source.jaffle_shop.customers + """, + ) + exc = DJException() + ctx = CompileContext(session=construction_session, exception=exc) + query.compile(ctx) + types = [ + BigIntType(), + BigIntType(), + ] + assert types == [exp.type for exp in query.select.projection] # type: ignore + + +def test_infer_types_coalesce(construction_session: Session): + """ + Test type inference of functions + """ + + query = parse( + """ + SELECT + COALESCE(5, NULL), + COALESCE("random", NULL) + FROM dbt.source.jaffle_shop.customers + """, + ) + exc = DJException() + ctx = CompileContext(session=construction_session, exception=exc) + query.compile(ctx) + types = [ + IntegerType(), + StringType(), + ] + assert types == [exp.type for exp in query.select.projection] # type: ignore + + +def test_infer_types_array_map(construction_session: Session): + """ + Test type inference for arrays and maps + """ + + query = parse( + """ + SELECT + ARRAY(5, 6, 7, 8), + MAP(1, 'a', 2, 'b', 3, 'c'), + MAP(1.0, 'a', 2.0, 'b', 3.0, 'c'), + MAP(CAST(1.0 AS DOUBLE), 'a', CAST(2.0 AS DOUBLE), 'b', CAST(3.0 AS DOUBLE), 'c') + FROM dbt.source.jaffle_shop.customers + """, + ) + exc = DJException() + ctx = CompileContext(session=construction_session, exception=exc) + query.compile(ctx) + types = [ + ListType(IntegerType()), + MapType(IntegerType(), StringType()), + MapType(FloatType(), StringType()), + MapType(DoubleType(), StringType()), + ] + assert types == [exp.type for exp in query.select.projection] # type: ignore + + query = parse( + """ + SELECT + MAP(1, 'a', 2, 3, 'c') + FROM dbt.source.jaffle_shop.customers + """, + ) + exc = DJException() + ctx = CompileContext(session=construction_session, exception=exc) + query.compile(ctx) + assert query.select.projection[0].type == MapType( # type: ignore + key_type=IntegerType(), + value_type=StringType(), + ) + + query = parse( + """ + SELECT + ARRAY(1, 'a') + FROM dbt.source.jaffle_shop.customers + """, + ) + exc = DJException() + ctx = CompileContext(session=construction_session, exception=exc) + query.compile(ctx) + with pytest.raises(DJParseException) as exc_info: + query.select.projection[0].type # type: ignore # pylint: disable=pointless-statement + assert "Multiple types int, string passed to array" in str(exc_info) + + +def test_infer_types_if(construction_session: Session): + """ + Test type inference of IF + """ + query = parse( + """ + SELECT + IF(1=2, 10, 20), + IF(1=2, 'random', 20), + IFNULL(1, 2, 3) + FROM dbt.source.jaffle_shop.customers + """, + ) + exc = DJException() + ctx = CompileContext(session=construction_session, exception=exc) + query.compile(ctx) + assert query.select.projection[0].type == IntegerType() # type: ignore + with pytest.raises(DJException) as exc_info: + query.select.projection[1].type # type: ignore # pylint: disable=pointless-statement + assert ( + "The then result and else result must match in type! Got string and int" + in str(exc_info) + ) + assert query.select.projection[2].type == IntegerType() # type: ignore + + +def test_infer_types_exp(construction_session: Session): + """ + Test type inference of math functions + """ + query = parse( + """ + SELECT + EXP(2), + FLOOR(22.1), + LENGTH('blah'), + LEVENSHTEIN('a', 'b'), + LN(5), + LOG(10, 100), + LOG2(2), + LOG10(100), + POW(1, 2), + POWER(1, 2), + ROUND(1.2, 0), + ROUND(1.2, -1), + ROUND(CAST(1.233 AS DOUBLE), 1), + ROUND(CAST(1.233 AS DECIMAL(8, 6)), 20), + CEIL(CAST(1.233 AS DECIMAL(8, 6))), + SQRT(12) + FROM dbt.source.jaffle_shop.customers + """, + ) + exc = DJException() + ctx = CompileContext(session=construction_session, exception=exc) + query.compile(ctx) + types = [ + DoubleType(), + BigIntType(), + IntegerType(), + IntegerType(), + DoubleType(), + DoubleType(), + DoubleType(), + DoubleType(), + DoubleType(), + DoubleType(), + IntegerType(), + FloatType(), + DoubleType(), + DecimalType(precision=9, scale=6), + DecimalType(precision=3, scale=0), + DoubleType(), + ] + assert types == [exp.type for exp in query.select.projection] # type: ignore + + +def test_infer_types_str(construction_session: Session): + """ + Test type inference of EXP + """ + query = parse( + """ + SELECT + LOWER('Extra'), + SUBSTRING('e14', 1, 1), + SUBSTRING('e14', 1, 1) + FROM dbt.source.jaffle_shop.customers + """, + ) + exc = DJException() + ctx = CompileContext(session=construction_session, exception=exc) + query.compile(ctx) + types = [ + StringType(), + StringType(), + StringType(), + ] + assert types == [exp.type for exp in query.select.projection] # type: ignore + + +def test_column_type_validation(): + """ + Test type inference of EXP + """ + with pytest.raises(DJException) as exc_info: + ColumnType.validate("decimal") + assert "DJ does not recognize the type `decimal`" in str(exc_info) + assert ColumnType.validate("decimal(10, 8)") == DecimalType(10, 8) + + assert ColumnType.validate("fixed(2)") == FixedType(2) + assert ColumnType.validate("map") == MapType( + StringType(), + StringType(), + ) + assert ColumnType.validate("array") == ListType(IntegerType()) + + +def test_infer_types_datetime(construction_session: Session): + """ + Test type inference of functions + """ + + query = parse( + """ + SELECT + CURRENT_DATE(), + CURRENT_TIME(), + CURRENT_TIMESTAMP(), + + NOW(), + + DATE_ADD('2020-01-01', 10), + DATE_ADD(CURRENT_DATE(), 10), + DATE_SUB('2020-01-01', 10), + DATE_SUB(CURRENT_DATE(), 10), + + DATEDIFF('2020-01-01', '2021-01-01'), + DATEDIFF(CURRENT_DATE(), CURRENT_DATE()), + + + EXTRACT(YEAR FROM '2020-01-01 00:00:00'), + EXTRACT(SECOND FROM '2020-01-01 00:00:00'), + + DAY("2022-01-01"), + MONTH("2022-01-01"), + WEEK("2022-01-01"), + YEAR("2022-01-01") + FROM dbt.source.jaffle_shop.customers + """, + ) + exc = DJException() + ctx = CompileContext(session=construction_session, exception=exc) + query.compile(ctx) + types = [ + DateType(), + TimeType(), + TimestampType(), + TimestampType(), + DateType(), + DateType(), + DateType(), + DateType(), + IntegerType(), + IntegerType(), + IntegerType(), + DecimalType(precision=8, scale=6), + IntegerType(), + BigIntType(), + BigIntType(), + BigIntType(), + ] + assert types == [exp.type for exp in query.select.projection] # type: ignore diff --git a/datajunction-server/tests/construction/utils_test.py b/datajunction-server/tests/construction/utils_test.py new file mode 100644 index 000000000..c2d18b62b --- /dev/null +++ b/datajunction-server/tests/construction/utils_test.py @@ -0,0 +1,46 @@ +""" +Tests for building nodes and extracting dependencies +""" + +# pylint: disable=too-many-lines +import pytest +from sqlmodel import Session + +from datajunction_server.construction.utils import get_dj_node +from datajunction_server.errors import DJErrorException +from datajunction_server.models.node import NodeType + + +def test_get_dj_node_raise_unknown_node_exception(session: Session): + """ + Test raising an unknown node exception when calling get_dj_node + """ + with pytest.raises(DJErrorException) as exc_info: + get_dj_node(session, "foobar") + + assert "No node" in str(exc_info.value) + + with pytest.raises(DJErrorException) as exc_info: + get_dj_node(session, "foobar", kinds={NodeType.METRIC, NodeType.DIMENSION}) + + assert "NodeType.DIMENSION" in str(exc_info.value) + assert "NodeType.METRIC" in str(exc_info.value) + assert "NodeType.SOURCE" not in str(exc_info.value) + assert "NodeType.TRANSFORM" not in str(exc_info.value) + + with pytest.raises(DJErrorException) as exc_info: + # test that the event_type raises because it's a dimension and not a transform + get_dj_node(session, "event_type", kinds={NodeType.TRANSFORM}) + + assert ( + "No node `event_type` exists of kind NodeType.TRANSFORM" # pylint: disable=C0301 + in str(exc_info.value) + ) + + # test that the event_type raises because it's a dimension and not a transform + with pytest.raises(DJErrorException) as exc_info: + get_dj_node(session, "event_type", kinds={NodeType.TRANSFORM}) + + assert "No node `event_type` exists of kind NodeType.TRANSFORM" in str( + exc_info.value, + ) diff --git a/datajunction-server/tests/errors_test.py b/datajunction-server/tests/errors_test.py new file mode 100644 index 000000000..c187f6ca0 --- /dev/null +++ b/datajunction-server/tests/errors_test.py @@ -0,0 +1,46 @@ +""" +Tests errors. +""" + +from http import HTTPStatus + +from datajunction_server.errors import DJError, DJException, ErrorCode + + +def test_dj_exception() -> None: + """ + Test the base ``DJException``. + """ + exc = DJException() + assert exc.dbapi_exception == "Error" + assert exc.http_status_code == 500 + + exc = DJException(dbapi_exception="InternalError") + assert exc.dbapi_exception == "InternalError" + assert exc.http_status_code == 500 + + exc = DJException( + dbapi_exception="ProgrammingError", + http_status_code=HTTPStatus.BAD_REQUEST, + ) + assert exc.dbapi_exception == "ProgrammingError" + assert exc.http_status_code == HTTPStatus.BAD_REQUEST + + exc = DJException("Message") + assert str(exc) == "Message" + exc = DJException( + "Message", + errors=[ + DJError(message="Error 1", code=ErrorCode.UNKNOWN_ERROR), + DJError(message="Error 2", code=ErrorCode.UNKNOWN_ERROR), + ], + ) + assert ( + str(exc) + == """Message +The following errors happened: +- Error 1 (error code: 0) +- Error 2 (error code: 0)""" + ) + + assert DJException("Message") == DJException("Message") diff --git a/datajunction-server/tests/examples.py b/datajunction-server/tests/examples.py new file mode 100644 index 000000000..39f8d3d7a --- /dev/null +++ b/datajunction-server/tests/examples.py @@ -0,0 +1,2454 @@ +# pylint: disable=too-many-lines,C0301 + +""" +Post requests for all example entities +""" +from datajunction_server.models import Column +from datajunction_server.models.query import QueryWithResults +from datajunction_server.sql.parsing.types import IntegerType, StringType, TimestampType +from datajunction_server.typing import QueryState + +EXAMPLES = ( # type: ignore + ( + "/catalogs/", + {"name": "draft"}, + ), + ( + "/catalogs/", + {"name": "default"}, + ), + ( + "/engines/", + {"name": "spark", "version": "3.1.1", "dialect": "spark"}, + ), + ( + "/catalogs/default/engines/", + [{"name": "spark", "version": "3.1.1", "dialect": "spark"}], + ), + ( + "/engines/", + {"name": "druid", "version": "", "dialect": "druid"}, + ), + ( + "/catalogs/default/engines/", + [{"name": "druid", "version": "", "dialect": "druid"}], + ), + ( + "/catalogs/", + {"name": "public"}, + ), + ( + "/engines/", + {"name": "postgres", "version": "15.2"}, + ), + ( + "/catalogs/public/engines/", + [{"name": "postgres", "version": "15.2"}], + ), + ( # DJ must be primed with a "default" namespace + "/namespaces/default/", + {}, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "repair_order_id", "type": "int"}, + {"name": "municipality_id", "type": "string"}, + {"name": "hard_hat_id", "type": "int"}, + {"name": "order_date", "type": "timestamp"}, + {"name": "required_date", "type": "timestamp"}, + {"name": "dispatched_date", "type": "timestamp"}, + {"name": "dispatcher_id", "type": "int"}, + ], + "description": "All repair orders", + "mode": "published", + "name": "default.repair_orders", + "catalog": "default", + "schema_": "roads", + "table": "repair_orders", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "repair_order_id", "type": "int"}, + {"name": "repair_type_id", "type": "int"}, + {"name": "price", "type": "float"}, + {"name": "quantity", "type": "int"}, + {"name": "discount", "type": "float"}, + ], + "description": "Details on repair orders", + "mode": "published", + "name": "default.repair_order_details", + "catalog": "default", + "schema_": "roads", + "table": "repair_order_details", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "repair_type_id", "type": "int"}, + {"name": "repair_type_name", "type": "string"}, + {"name": "contractor_id", "type": "int"}, + ], + "description": "Information on types of repairs", + "mode": "published", + "name": "default.repair_type", + "catalog": "default", + "schema_": "roads", + "table": "repair_type", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "contractor_id", "type": "int"}, + {"name": "company_name", "type": "string"}, + {"name": "contact_name", "type": "string"}, + {"name": "contact_title", "type": "string"}, + {"name": "address", "type": "string"}, + {"name": "city", "type": "string"}, + {"name": "state", "type": "string"}, + {"name": "postal_code", "type": "string"}, + {"name": "country", "type": "string"}, + {"name": "phone", "type": "string"}, + ], + "description": "Information on contractors", + "mode": "published", + "name": "default.contractors", + "catalog": "default", + "schema_": "roads", + "table": "contractors", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "municipality_id", "type": "string"}, + {"name": "municipality_type_id", "type": "string"}, + ], + "description": "Lookup table for municipality and municipality types", + "mode": "published", + "name": "default.municipality_municipality_type", + "catalog": "default", + "schema_": "roads", + "table": "municipality_municipality_type", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "municipality_type_id", "type": "string"}, + {"name": "municipality_type_desc", "type": "string"}, + ], + "description": "Information on municipality types", + "mode": "published", + "name": "default.municipality_type", + "catalog": "default", + "schema_": "roads", + "table": "municipality_type", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "municipality_id", "type": "string"}, + {"name": "contact_name", "type": "string"}, + {"name": "contact_title", "type": "string"}, + {"name": "local_region", "type": "string"}, + {"name": "phone", "type": "string"}, + {"name": "state_id", "type": "int"}, + ], + "description": "Information on municipalities", + "mode": "published", + "name": "default.municipality", + "catalog": "default", + "schema_": "roads", + "table": "municipality", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "dispatcher_id", "type": "int"}, + {"name": "company_name", "type": "string"}, + {"name": "phone", "type": "string"}, + ], + "description": "Information on dispatchers", + "mode": "published", + "name": "default.dispatchers", + "catalog": "default", + "schema_": "roads", + "table": "dispatchers", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "hard_hat_id", "type": "int"}, + {"name": "last_name", "type": "string"}, + {"name": "first_name", "type": "string"}, + {"name": "title", "type": "string"}, + {"name": "birth_date", "type": "timestamp"}, + {"name": "hire_date", "type": "timestamp"}, + {"name": "address", "type": "string"}, + {"name": "city", "type": "string"}, + {"name": "state", "type": "string"}, + {"name": "postal_code", "type": "string"}, + {"name": "country", "type": "string"}, + {"name": "manager", "type": "int"}, + {"name": "contractor_id", "type": "int"}, + ], + "description": "Information on employees", + "mode": "published", + "name": "default.hard_hats", + "catalog": "default", + "schema_": "roads", + "table": "hard_hats", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "hard_hat_id", "type": "int"}, + {"name": "state_id", "type": "string"}, + ], + "description": "Lookup table for employee's current state", + "mode": "published", + "name": "default.hard_hat_state", + "catalog": "default", + "schema_": "roads", + "table": "hard_hat_state", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "state_id", "type": "int"}, + {"name": "state_name", "type": "string"}, + {"name": "state_abbr", "type": "string"}, + {"name": "state_region", "type": "int"}, + ], + "description": "Information on different types of repairs", + "mode": "published", + "name": "default.us_states", + "catalog": "default", + "schema_": "roads", + "table": "us_states", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "us_region_id", "type": "int"}, + {"name": "us_region_description", "type": "string"}, + ], + "description": "Information on US regions", + "mode": "published", + "name": "default.us_region", + "catalog": "default", + "schema_": "roads", + "table": "us_region", + }, + ), + ( + "/nodes/dimension/", + { + "description": "Repair order dimension", + "query": """ + SELECT + repair_order_id, + municipality_id, + hard_hat_id, + order_date, + required_date, + dispatched_date, + dispatcher_id + FROM default.repair_orders + """, + "mode": "published", + "name": "default.repair_order", + "primary_key": ["repair_order_id"], + }, + ), + ( + "/nodes/dimension/", + { + "description": "Contractor dimension", + "query": """ + SELECT + contractor_id, + company_name, + contact_name, + contact_title, + address, + city, + state, + postal_code, + country, + phone + FROM default.contractors + """, + "mode": "published", + "name": "default.contractor", + "primary_key": ["contractor_id"], + }, + ), + ( + "/nodes/dimension/", + { + "description": "Hard hat dimension", + "query": """ + SELECT + hard_hat_id, + last_name, + first_name, + title, + birth_date, + hire_date, + address, + city, + state, + postal_code, + country, + manager, + contractor_id + FROM default.hard_hats + """, + "mode": "published", + "name": "default.hard_hat", + "primary_key": ["hard_hat_id"], + }, + ), + ( + "/nodes/dimension/", + { + "description": "Hard hat dimension", + "query": """ + SELECT + hh.hard_hat_id, + last_name, + first_name, + title, + birth_date, + hire_date, + address, + city, + state, + postal_code, + country, + manager, + contractor_id, + hhs.state_id AS state_id + FROM default.hard_hats hh + LEFT JOIN default.hard_hat_state hhs + ON hh.hard_hat_id = hhs.hard_hat_id + WHERE hh.state_id = 'NY' + """, + "mode": "published", + "name": "default.local_hard_hats", + "primary_key": ["hard_hat_id"], + }, + ), + ( + "/nodes/dimension/", + { + "description": "US state dimension", + "query": """ + SELECT + state_id, + state_name, + state_abbr AS state_short, + state_region, + r.us_region_description AS state_region_description + FROM default.us_states s + LEFT JOIN default.us_region r + ON s.state_region = r.us_region_id + """, + "mode": "published", + "name": "default.us_state", + "primary_key": ["state_id"], + }, + ), + ( + "/nodes/dimension/", + { + "description": "Dispatcher dimension", + "query": """ + SELECT + dispatcher_id, + company_name, + phone + FROM default.dispatchers + """, + "mode": "published", + "name": "default.dispatcher", + "primary_key": ["dispatcher_id"], + }, + ), + ( + "/nodes/dimension/", + { + "description": "Municipality dimension", + "query": """ + SELECT + m.municipality_id AS municipality_id, + contact_name, + contact_title, + local_region, + state_id, + mmt.municipality_type_id AS municipality_type_id, + mt.municipality_type_desc AS municipality_type_desc + FROM default.municipality AS m + LEFT JOIN default.municipality_municipality_type AS mmt + ON m.municipality_id = mmt.municipality_id + LEFT JOIN default.municipality_type AS mt + ON mmt.municipality_type_id = mt.municipality_type_desc + """, + "mode": "published", + "name": "default.municipality_dim", + "primary_key": ["municipality_id"], + }, + ), + ( + "/nodes/transform/", + { + "name": "default.regional_level_agg", + "description": "Regional-level aggregates", + "mode": "published", + "primary_key": [ + "us_region_id", + "state_name", + "order_year", + "order_month", + "order_day", + ], + "query": """SELECT + usr.us_region_id, + us.state_name, + CONCAT(us.state_name, '-', usr.us_region_description) AS location_hierarchy, + EXTRACT(YEAR FROM ro.order_date) AS order_year, + EXTRACT(MONTH FROM ro.order_date) AS order_month, + EXTRACT(DAY FROM ro.order_date) AS order_day, + COUNT(DISTINCT CASE WHEN ro.dispatched_date IS NOT NULL THEN ro.repair_order_id ELSE NULL END) AS completed_repairs, + COUNT(DISTINCT ro.repair_order_id) AS total_repairs_dispatched, + SUM(rd.price * rd.quantity) AS total_amount_in_region, + AVG(rd.price * rd.quantity) AS avg_repair_amount_in_region, + -- ELEMENT_AT(ARRAY_SORT(COLLECT_LIST(STRUCT(COUNT(*) AS cnt, rt.repair_type_name AS repair_type_name)), (left, right) -> case when left.cnt < right.cnt then 1 when left.cnt > right.cnt then -1 else 0 end), 0).repair_type_name AS most_common_repair_type, + AVG(DATEDIFF(ro.dispatched_date, ro.order_date)) AS avg_dispatch_delay, + COUNT(DISTINCT c.contractor_id) AS unique_contractors +FROM + (SELECT + repair_order_id, + municipality_id, + hard_hat_id, + order_date, + required_date, + dispatched_date, + dispatcher_id + FROM default.repair_orders) ro +JOIN + default.municipality m ON ro.municipality_id = m.municipality_id +JOIN + default.us_states us ON m.state_id = us.state_id + AND AVG(rd.price * rd.quantity) > + (SELECT AVG(price * quantity) FROM default.repair_order_details WHERE repair_order_id = ro.repair_order_id) +JOIN + default.us_states us ON m.state_id = us.state_id +JOIN + default.us_region usr ON us.state_region = usr.us_region_id +JOIN + default.repair_order_details rd ON ro.repair_order_id = rd.repair_order_id +JOIN + default.repair_type rt ON rd.repair_type_id = rt.repair_type_id +JOIN + default.contractors c ON rt.contractor_id = c.contractor_id +GROUP BY + usr.us_region_id, + EXTRACT(YEAR FROM ro.order_date), + EXTRACT(MONTH FROM ro.order_date), + EXTRACT(DAY FROM ro.order_date)""", + }, + ), + ( + "/nodes/transform/", + { + "description": "National level aggregates", + "name": "default.national_level_agg", + "mode": "published", + "query": "SELECT SUM(rd.price * rd.quantity) AS total_amount_nationwide FROM default.repair_order_details rd", + }, + ), + ( + "/nodes/metric/", + { + "description": """For each US region (as defined in the us_region table), we want to calculate: + Regional Repair Efficiency = (Number of Completed Repairs / Total Repairs Dispatched) × + (Total Repair Amount in Region / Total Repair Amount Nationwide) × 100 + Here: + A "Completed Repair" is one where the dispatched_date is not null. + "Total Repair Amount in Region" is the total amount spent on repairs in a given region. + "Total Repair Amount Nationwide" is the total amount spent on all repairs nationwide.""", + "name": "default.regional_repair_efficiency", + "query": """SELECT + (SUM(rm.completed_repairs) * 1.0 / SUM(rm.total_repairs_dispatched)) * + (SUM(rm.total_amount_in_region) * 1.0 / SUM(na.total_amount_nationwide)) * 100 +FROM + default.regional_level_agg rm +CROSS JOIN + default.national_level_agg na""", + "mode": "published", + }, + ), + ( + "/nodes/metric/", + { + "description": "Number of repair orders", + "query": ("SELECT count(repair_order_id) " "FROM default.repair_orders"), + "mode": "published", + "name": "default.num_repair_orders", + }, + ), + ( + "/nodes/metric/", + { + "description": "Average repair price", + "query": ( + "SELECT avg(price) as default_DOT_avg_repair_price " + "FROM default.repair_order_details" + ), + "mode": "published", + "name": "default.avg_repair_price", + }, + ), + ( + "/nodes/metric/", + { + "description": "Total repair cost", + "query": "SELECT sum(price) FROM default.repair_order_details", + "mode": "published", + "name": "default.total_repair_cost", + }, + ), + ( + "/nodes/metric/", + { + "description": "Average length of employment", + "query": ( + "SELECT avg(NOW() - hire_date) as default_DOT_avg_length_of_employment " + "FROM default.hard_hats" + ), + "mode": "published", + "name": "default.avg_length_of_employment", + }, + ), + ( + "/nodes/metric/", + { + "name": "default.discounted_orders_rate", + "query": ( + """ + SELECT + cast(sum(if(discount > 0.0, 1, 0)) as double) / count(*) + AS default_DOT_discounted_orders_rate + FROM default.repair_order_details + """ + ), + "mode": "published", + "description": "Proportion of Discounted Orders", + }, + ), + ( + "/nodes/metric/", + { + "description": "Total repair order discounts", + "query": ( + "SELECT sum(price * discount) " "FROM default.repair_order_details" + ), + "mode": "published", + "name": "default.total_repair_order_discounts", + }, + ), + ( + "/nodes/metric/", + { + "description": "Total repair order discounts", + "query": ( + "SELECT avg(price * discount) " "FROM default.repair_order_details" + ), + "mode": "published", + "name": "default.avg_repair_order_discounts", + }, + ), + ( + "/nodes/metric/", + { + "description": "Average time to dispatch a repair order", + "query": ( + "SELECT avg(dispatched_date - order_date) " "FROM default.repair_orders" + ), + "mode": "published", + "name": "default.avg_time_to_dispatch", + }, + ), + ( + ( + "/nodes/default.repair_order_details/columns/repair_order_id/" + "?dimension=default.repair_order&dimension_column=repair_order_id" + ), + {}, + ), + ( + ( + "/nodes/default.repair_type/columns/contractor_id/" + "?dimension=default.contractor&dimension_column=contractor_id" + ), + {}, + ), + ( + ( + "/nodes/default.repair_orders/columns/repair_order_id/" + "?dimension=default.repair_order&dimension_column=repair_order_id" + ), + {}, + ), + ( + ( + "/nodes/default.hard_hat/columns/state/" + "?dimension=default.us_state&dimension_column=state_short" + ), + {}, + ), + ( + ( + "/nodes/default.repair_order_details/columns/repair_order_id/" + "?dimension=default.repair_order&dimension_column=repair_order_id" + ), + {}, + ), + ( + ( + "/nodes/default.repair_order/columns/dispatcher_id/" + "?dimension=default.dispatcher&dimension_column=dispatcher_id" + ), + {}, + ), + ( + ( + "/nodes/default.repair_order/columns/hard_hat_id/" + "?dimension=default.hard_hat&dimension_column=hard_hat_id" + ), + {}, + ), + ( + ( + "/nodes/default.repair_order/columns/municipality_id/" + "?dimension=default.municipality_dim&dimension_column=municipality_id" + ), + {}, + ), + ( # foo.bar Namespaced copy of roads database example + "/namespaces/foo.bar/", + {}, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "repair_order_id", "type": "int"}, + {"name": "municipality_id", "type": "string"}, + {"name": "hard_hat_id", "type": "int"}, + {"name": "order_date", "type": "timestamp"}, + {"name": "required_date", "type": "timestamp"}, + {"name": "dispatched_date", "type": "timestamp"}, + {"name": "dispatcher_id", "type": "int"}, + ], + "description": "All repair orders", + "mode": "published", + "name": "foo.bar.repair_orders", + "catalog": "default", + "schema_": "roads", + "table": "repair_orders", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "repair_order_id", "type": "int"}, + {"name": "repair_type_id", "type": "int"}, + {"name": "price", "type": "float"}, + {"name": "quantity", "type": "int"}, + {"name": "discount", "type": "float"}, + ], + "description": "Details on repair orders", + "mode": "published", + "name": "foo.bar.repair_order_details", + "catalog": "default", + "schema_": "roads", + "table": "repair_order_details", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "repair_type_id", "type": "int"}, + {"name": "repair_type_name", "type": "string"}, + {"name": "contractor_id", "type": "int"}, + ], + "description": "Information on types of repairs", + "mode": "published", + "name": "foo.bar.repair_type", + "catalog": "default", + "schema_": "roads", + "table": "repair_type", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "contractor_id", "type": "int"}, + {"name": "company_name", "type": "string"}, + {"name": "contact_name", "type": "string"}, + {"name": "contact_title", "type": "string"}, + {"name": "address", "type": "string"}, + {"name": "city", "type": "string"}, + {"name": "state", "type": "string"}, + {"name": "postal_code", "type": "string"}, + {"name": "country", "type": "string"}, + {"name": "phone", "type": "string"}, + ], + "description": "Information on contractors", + "mode": "published", + "name": "foo.bar.contractors", + "catalog": "default", + "schema_": "roads", + "table": "contractors", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "municipality_id", "type": "string"}, + {"name": "municipality_type_id", "type": "string"}, + ], + "description": "Lookup table for municipality and municipality types", + "mode": "published", + "name": "foo.bar.municipality_municipality_type", + "catalog": "default", + "schema_": "roads", + "table": "municipality_municipality_type", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "municipality_type_id", "type": "string"}, + {"name": "municipality_type_desc", "type": "string"}, + ], + "description": "Information on municipality types", + "mode": "published", + "name": "foo.bar.municipality_type", + "catalog": "default", + "schema_": "roads", + "table": "municipality_type", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "municipality_id", "type": "string"}, + {"name": "contact_name", "type": "string"}, + {"name": "contact_title", "type": "string"}, + {"name": "local_region", "type": "string"}, + {"name": "phone", "type": "string"}, + {"name": "state_id", "type": "int"}, + ], + "description": "Information on municipalities", + "mode": "published", + "name": "foo.bar.municipality", + "catalog": "default", + "schema_": "roads", + "table": "municipality", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "dispatcher_id", "type": "int"}, + {"name": "company_name", "type": "string"}, + {"name": "phone", "type": "string"}, + ], + "description": "Information on dispatchers", + "mode": "published", + "name": "foo.bar.dispatchers", + "catalog": "default", + "schema_": "roads", + "table": "dispatchers", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "hard_hat_id", "type": "int"}, + {"name": "last_name", "type": "string"}, + {"name": "first_name", "type": "string"}, + {"name": "title", "type": "string"}, + {"name": "birth_date", "type": "timestamp"}, + {"name": "hire_date", "type": "timestamp"}, + {"name": "address", "type": "string"}, + {"name": "city", "type": "string"}, + {"name": "state", "type": "string"}, + {"name": "postal_code", "type": "string"}, + {"name": "country", "type": "string"}, + {"name": "manager", "type": "int"}, + {"name": "contractor_id", "type": "int"}, + ], + "description": "Information on employees", + "mode": "published", + "name": "foo.bar.hard_hats", + "catalog": "default", + "schema_": "roads", + "table": "hard_hats", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "hard_hat_id", "type": "int"}, + {"name": "state_id", "type": "string"}, + ], + "description": "Lookup table for employee's current state", + "mode": "published", + "name": "foo.bar.hard_hat_state", + "catalog": "default", + "schema_": "roads", + "table": "hard_hat_state", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "state_id", "type": "int"}, + {"name": "state_name", "type": "string"}, + {"name": "state_abbr", "type": "string"}, + {"name": "state_region", "type": "int"}, + ], + "description": "Information on different types of repairs", + "mode": "published", + "name": "foo.bar.us_states", + "catalog": "default", + "schema_": "roads", + "table": "us_states", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "us_region_id", "type": "int"}, + {"name": "us_region_description", "type": "string"}, + ], + "description": "Information on US regions", + "mode": "published", + "name": "foo.bar.us_region", + "catalog": "default", + "schema_": "roads", + "table": "us_region", + }, + ), + ( + "/nodes/dimension/", + { + "description": "Repair order dimension", + "query": """ + SELECT + repair_order_id, + municipality_id, + hard_hat_id, + order_date, + required_date, + dispatched_date, + dispatcher_id + FROM foo.bar.repair_orders + """, + "mode": "published", + "name": "foo.bar.repair_order", + "primary_key": ["repair_order_id"], + }, + ), + ( + "/nodes/dimension/", + { + "description": "Contractor dimension", + "query": """ + SELECT + contractor_id, + company_name, + contact_name, + contact_title, + address, + city, + state, + postal_code, + country, + phone + FROM foo.bar.contractors + """, + "mode": "published", + "name": "foo.bar.contractor", + "primary_key": ["contractor_id"], + }, + ), + ( + "/nodes/dimension/", + { + "description": "Hard hat dimension", + "query": """ + SELECT + hard_hat_id, + last_name, + first_name, + title, + birth_date, + hire_date, + address, + city, + state, + postal_code, + country, + manager, + contractor_id + FROM foo.bar.hard_hats + """, + "mode": "published", + "name": "foo.bar.hard_hat", + "primary_key": ["hard_hat_id"], + }, + ), + ( + "/nodes/dimension/", + { + "description": "Hard hat dimension", + "query": """ + SELECT + hh.hard_hat_id, + last_name, + first_name, + title, + birth_date, + hire_date, + address, + city, + state, + postal_code, + country, + manager, + contractor_id, + hhs.state_id AS state_id + FROM foo.bar.hard_hats hh + LEFT JOIN foo.bar.hard_hat_state hhs + ON hh.hard_hat_id = hhs.hard_hat_id + WHERE hh.state_id = 'NY' + """, + "mode": "published", + "name": "foo.bar.local_hard_hats", + "primary_key": ["hard_hat_id"], + }, + ), + ( + "/nodes/dimension/", + { + "description": "US state dimension", + "query": """ + SELECT + state_id, + state_name, + state_abbr, + state_region, + r.us_region_description AS state_region_description + FROM foo.bar.us_states s + LEFT JOIN foo.bar.us_region r + ON s.state_region = r.us_region_id + """, + "mode": "published", + "name": "foo.bar.us_state", + "primary_key": ["state_id"], + }, + ), + ( + "/nodes/dimension/", + { + "description": "Dispatcher dimension", + "query": """ + SELECT + dispatcher_id, + company_name, + phone + FROM foo.bar.dispatchers + """, + "mode": "published", + "name": "foo.bar.dispatcher", + "primary_key": ["dispatcher_id"], + }, + ), + ( + "/nodes/dimension/", + { + "description": "Municipality dimension", + "query": """ + SELECT + m.municipality_id AS municipality_id, + contact_name, + contact_title, + local_region, + state_id, + mmt.municipality_type_id AS municipality_type_id, + mt.municipality_type_desc AS municipality_type_desc + FROM foo.bar.municipality AS m + LEFT JOIN foo.bar.municipality_municipality_type AS mmt + ON m.municipality_id = mmt.municipality_id + LEFT JOIN foo.bar.municipality_type AS mt + ON mmt.municipality_type_id = mt.municipality_type_desc + """, + "mode": "published", + "name": "foo.bar.municipality_dim", + "primary_key": ["municipality_id"], + }, + ), + ( + "/nodes/metric/", + { + "description": "Number of repair orders", + "query": ( + "SELECT count(repair_order_id) as foo_DOT_bar_DOT_num_repair_orders " + "FROM foo.bar.repair_orders" + ), + "mode": "published", + "name": "foo.bar.num_repair_orders", + }, + ), + ( + "/nodes/metric/", + { + "description": "Average repair price", + "query": "SELECT avg(price) FROM foo.bar.repair_order_details", + "mode": "published", + "name": "foo.bar.avg_repair_price", + }, + ), + ( + "/nodes/metric/", + { + "description": "Total repair cost", + "query": "SELECT sum(price) FROM foo.bar.repair_order_details", + "mode": "published", + "name": "foo.bar.total_repair_cost", + }, + ), + ( + "/nodes/metric/", + { + "description": "Average length of employment", + "query": ("SELECT avg(NOW() - hire_date) " "FROM foo.bar.hard_hats"), + "mode": "published", + "name": "foo.bar.avg_length_of_employment", + }, + ), + ( + "/nodes/metric/", + { + "description": "Total repair order discounts", + "query": ( + "SELECT sum(price * discount) " "FROM foo.bar.repair_order_details" + ), + "mode": "published", + "name": "foo.bar.total_repair_order_discounts", + }, + ), + ( + "/nodes/metric/", + { + "description": "Total repair order discounts", + "query": ( + "SELECT avg(price * discount) " "FROM foo.bar.repair_order_details" + ), + "mode": "published", + "name": "foo.bar.avg_repair_order_discounts", + }, + ), + ( + "/nodes/metric/", + { + "description": "Average time to dispatch a repair order", + "query": ( + "SELECT avg(dispatched_date - order_date) " "FROM foo.bar.repair_orders" + ), + "mode": "published", + "name": "foo.bar.avg_time_to_dispatch", + }, + ), + ( + ( + "/nodes/foo.bar.repair_order_details/columns/repair_order_id/" + "?dimension=foo.bar.repair_order&dimension_column=repair_order_id" + ), + {}, + ), + ( + ( + "/nodes/foo.bar.repair_type/columns/contractor_id/" + "?dimension=foo.bar.contractor&dimension_column=contractor_id" + ), + {}, + ), + ( + ( + "/nodes/foo.bar.repair_orders/columns/repair_order_id/" + "?dimension=foo.bar.repair_order&dimension_column=repair_order_id" + ), + {}, + ), + ( + ( + "/nodes/foo.bar.repair_order_details/columns/repair_order_id/" + "?dimension=foo.bar.repair_order&dimension_column=repair_order_id" + ), + {}, + ), + ( + ( + "/nodes/foo.bar.repair_order/columns/dispatcher_id/" + "?dimension=foo.bar.dispatcher&dimension_column=dispatcher_id" + ), + {}, + ), + ( + ( + "/nodes/foo.bar.repair_order/columns/hard_hat_id/" + "?dimension=foo.bar.hard_hat&dimension_column=hard_hat_id" + ), + {}, + ), + ( + ( + "/nodes/foo.bar.repair_order/columns/municipality_id/" + "?dimension=foo.bar.municipality_dim&dimension_column=municipality_id" + ), + {}, + ), + ( # Accounts/Revenue examples begin + "/nodes/source/", + { + "columns": [ + {"name": "id", "type": "int"}, + {"name": "account_type_name", "type": "string"}, + {"name": "account_type_classification", "type": "int"}, + {"name": "preferred_payment_method", "type": "int"}, + ], + "description": "A source table for account type data", + "mode": "published", + "name": "default.account_type_table", + "catalog": "default", + "schema_": "accounting", + "table": "account_type_table", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "id", "type": "int"}, + {"name": "payment_type_name", "type": "string"}, + {"name": "payment_type_classification", "type": "string"}, + ], + "description": "A source table for different types of payments", + "mode": "published", + "name": "default.payment_type_table", + "catalog": "default", + "schema_": "accounting", + "table": "payment_type_table", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "payment_id", "type": "int"}, + {"name": "payment_amount", "type": "float"}, + {"name": "payment_type", "type": "int"}, + {"name": "customer_id", "type": "int"}, + {"name": "account_type", "type": "string"}, + ], + "description": "All repair orders", + "mode": "published", + "name": "default.revenue", + "catalog": "default", + "schema_": "accounting", + "table": "revenue", + }, + ), + ( + "/nodes/dimension/", + { + "description": "Payment type dimensions", + "query": ( + "SELECT id, payment_type_name, payment_type_classification " + "FROM default.payment_type_table" + ), + "mode": "published", + "name": "default.payment_type", + "primary_key": ["id"], + }, + ), + ( + "/nodes/dimension/", + { + "description": "Account type dimension", + "query": ( + "SELECT id, account_type_name, " + "account_type_classification FROM " + "default.account_type_table" + ), + "mode": "published", + "name": "default.account_type", + "primary_key": ["id"], + }, + ), + ( + "/nodes/transform/", + { + "query": ( + "SELECT payment_id, payment_amount, customer_id, account_type " + "FROM default.revenue WHERE payment_amount > 1000000" + ), + "description": "Only large revenue payments", + "mode": "published", + "name": "default.large_revenue_payments_only", + }, + ), + ( + "/nodes/transform/", + { + "query": ( + "SELECT payment_id, payment_amount, customer_id, account_type " + "FROM default.revenue WHERE " + "large_revenue_payments_and_business_only > 1000000 " + "AND account_type='BUSINESS'" + ), + "description": "Only large revenue payments from business accounts", + "mode": "published", + "name": "default.large_revenue_payments_and_business_only", + }, + ), + ( + "/nodes/metric/", + { + "description": "Total number of account types", + "query": "SELECT count(id) FROM default.account_type", + "mode": "published", + "name": "default.number_of_account_types", + }, + ), + ( + "/namespaces/basic/", + {}, + ), + ( + "/namespaces/basic.source/", + {}, + ), + ( + "/namespaces/basic.transform/", + {}, + ), + ( + "/namespaces/basic.dimension/", + {}, + ), + ( + "/nodes/source/", + { + "name": "basic.source.users", + "description": "A user table", + "columns": [ + {"name": "id", "type": "int"}, + {"name": "full_name", "type": "string"}, + {"name": "age", "type": "int"}, + {"name": "country", "type": "string"}, + {"name": "gender", "type": "string"}, + {"name": "preferred_language", "type": "string"}, + {"name": "secret_number", "type": "float"}, + {"name": "created_at", "type": "timestamp"}, + {"name": "post_processing_timestamp", "type": "timestamp"}, + ], + "mode": "published", + "catalog": "public", + "schema_": "basic", + "table": "dim_users", + }, + ), + ( + "/nodes/dimension/", + { + "description": "User dimension", + "query": ( + "SELECT id, full_name, age, country, gender, preferred_language, " + "secret_number, created_at, post_processing_timestamp " + "FROM basic.source.users" + ), + "mode": "published", + "name": "basic.dimension.users", + "primary_key": ["id"], + }, + ), + ( + "/nodes/source/", + { + "name": "basic.source.comments", + "description": "A fact table with comments", + "columns": [ + {"name": "id", "type": "int"}, + { + "name": "user_id", + "type": "int", + "dimension": "basic.dimension.users", + }, + {"name": "timestamp", "type": "timestamp"}, + {"name": "text", "type": "string"}, + {"name": "event_timestamp", "type": "timestamp"}, + {"name": "created_at", "type": "timestamp"}, + {"name": "post_processing_timestamp", "type": "timestamp"}, + ], + "mode": "published", + "catalog": "public", + "schema_": "basic", + "table": "comments", + }, + ), + ( + "/nodes/dimension/", + { + "description": "Country dimension", + "query": "SELECT country, COUNT(1) AS user_cnt " + "FROM basic.source.users GROUP BY country", + "mode": "published", + "name": "basic.dimension.countries", + "primary_key": ["country"], + }, + ), + ( + "/nodes/transform/", + { + "description": "Country level agg table", + "query": ( + "SELECT country, COUNT(DISTINCT id) AS num_users " + "FROM basic.source.users GROUP BY 1" + ), + "mode": "published", + "name": "basic.transform.country_agg", + }, + ), + ( + "/nodes/metric/", + { + "description": "Number of comments", + "query": ("SELECT COUNT(1) FROM basic.source.comments"), + "mode": "published", + "name": "basic.num_comments", + }, + ), + ( + "/nodes/metric/", + { + "description": "Number of users.", + "type": "metric", + "query": ("SELECT SUM(num_users) FROM basic.transform.country_agg"), + "mode": "published", + "name": "basic.num_users", + }, + ), + ( # Event examples + "/nodes/source/", + { + "name": "default.event_source", + "description": "Events", + "columns": [ + {"name": "event_id", "type": "int"}, + {"name": "event_latency", "type": "int"}, + {"name": "device_id", "type": "int"}, + {"name": "country", "type": "string"}, + ], + "mode": "published", + "catalog": "default", + "schema_": "logs", + "table": "log_events", + }, + ), + ( + "/nodes/transform/", + { + "name": "default.long_events", + "description": "High-Latency Events", + "query": "SELECT event_id, event_latency, device_id, country " + "FROM default.event_source WHERE event_latency > 1000000", + "mode": "published", + }, + ), + ( + "/nodes/dimension/", + { + "name": "default.country_dim", + "description": "Country Dimension", + "query": "SELECT country, COUNT(DISTINCT event_id) AS events_cnt " + "FROM default.event_source GROUP BY country", + "mode": "published", + "primary_key": ["country"], + }, + ), + ( + ( + "/nodes/default.event_source/columns/country/?" + "dimension=default.country_dim&dimension_column=country" + ), + {}, + ), + ( + "/nodes/metric/", + { + "name": "default.device_ids_count", + "description": "Number of Distinct Devices", + "query": "SELECT COUNT(DISTINCT device_id) " "FROM default.event_source", + "mode": "published", + }, + ), + ( + "/nodes/metric/", + { + "name": "default.long_events_distinct_countries", + "description": "Number of Distinct Countries for Long Events", + "query": "SELECT COUNT(DISTINCT country) " "FROM default.long_events", + "mode": "published", + }, + ), + ( + "/namespaces/dbt.source/", + {}, + ), + ( + "/namespaces/dbt.source.jaffle_shop/", + {}, + ), + ( + "/namespaces/dbt.transform/", + {}, + ), + ( + "/namespaces/dbt.dimension/", + {}, + ), + ( + "/namespaces/dbt.source.stripe/", + {}, + ), + ( # DBT examples + "/nodes/source/", + { + "columns": [ + {"name": "id", "type": "int"}, + {"name": "first_name", "type": "string"}, + {"name": "last_name", "type": "string"}, + ], + "description": "Customer table", + "mode": "published", + "name": "dbt.source.jaffle_shop.customers", + "catalog": "public", + "schema_": "jaffle_shop", + "table": "customers", + }, + ), + ( + "/nodes/dimension/", + { + "description": "User dimension", + "query": ( + "SELECT id, first_name, last_name " + "FROM dbt.source.jaffle_shop.customers" + ), + "mode": "published", + "name": "dbt.dimension.customers", + "primary_key": ["id"], + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "id", "type": "int"}, + { + "name": "user_id", + "type": "int", + "dimension": "dbt.dimension.customers", + }, + {"name": "order_date", "type": "date"}, + {"name": "status", "type": "string"}, + {"name": "_etl_loaded_at", "type": "timestamp"}, + ], + "description": "Orders fact table", + "mode": "published", + "name": "dbt.source.jaffle_shop.orders", + "catalog": "public", + "schema_": "jaffle_shop", + "table": "orders", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "id", "type": "int"}, + {"name": "orderid", "type": "int"}, + {"name": "paymentmethod", "type": "string"}, + {"name": "status", "type": "string"}, + {"name": "amount", "type": "int"}, + {"name": "created", "type": "date"}, + {"name": "_batched_at", "type": "timestamp"}, + ], + "description": "Payments fact table.", + "mode": "published", + "name": "dbt.source.stripe.payments", + "catalog": "public", + "schema_": "stripe", + "table": "payments", + }, + ), + ( + "/nodes/transform/", + { + "query": ( + "SELECT c.id, " + " c.first_name, " + " c.last_name, " + " COUNT(1) AS order_cnt " + "FROM dbt.source.jaffle_shop.orders o " + "JOIN dbt.source.jaffle_shop.customers c ON o.user_id = c.id " + "GROUP BY c.id, " + " c.first_name, " + " c.last_name " + ), + "description": "Country level agg table", + "mode": "published", + "name": "dbt.transform.customer_agg", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "id", "type": "int"}, + {"name": "item_name", "type": "string"}, + {"name": "sold_count", "type": "int"}, + {"name": "price_per_unit", "type": "float"}, + {"name": "psp", "type": "string"}, + ], + "description": "A source table for sales", + "mode": "published", + "name": "default.sales", + "catalog": "default", + "schema_": "revenue", + "table": "sales", + }, + ), + ( + "/nodes/dimension/", + { + "description": "Item dimension", + "query": ( + "SELECT item_name " "account_type_classification FROM default.sales" + ), + "mode": "published", + "name": "default.items", + "primary_key": ["account_type_classification"], + }, + ), + ( + "/nodes/metric/", + { + "description": "Total units sold", + "query": "SELECT SUM(sold_count) as default_DOT_items_sold_count FROM default.sales", + "mode": "published", + "name": "default.items_sold_count", + }, + ), + ( + "/nodes/metric/", + { + "description": "Total profit", + "query": "SELECT SUM(sold_count * price_per_unit) FROM default.sales", + "mode": "published", + "name": "default.total_profit", + }, + ), + # lateral view explode/cross join unnest examples + ( + "/nodes/source/", + { + "columns": [ + {"name": "id", "type": "int"}, + {"name": "painter", "type": "string"}, + { + "name": "colors", + "type": "map", + }, + ], + "description": "Murals", + "mode": "published", + "name": "basic.murals", + "catalog": "public", + "schema_": "basic", + "table": "murals", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "color_id", "type": "int"}, + {"name": "color_name", "type": "string"}, + { + "name": "opacity", + "type": "float", + }, + { + "name": "luminosity", + "type": "float", + }, + { + "name": "garishness", + "type": "float", + }, + ], + "description": "Patch", + "mode": "published", + "name": "basic.patches", + "catalog": "public", + "schema_": "basic", + "table": "patches", + }, + ), + ( + "/nodes/transform/", + { + "query": """ + SELECT + cast(color_id as varchar) color_id, + color_name, + opacity, + luminosity, + garishness + FROM basic.patches + """, + "description": "Corrected patches", + "mode": "published", + "name": "basic.corrected_patches", + }, + ), + ( + "/nodes/dimension/", + { + "query": """ + SELECT + id AS mural_id, + t.color_id, + t.color_name color_name + FROM + ( + select + id, + colors + from basic.murals + ) murals + CROSS JOIN UNNEST(colors) AS t(color_id, color_name) + """, + "description": "Mural paint colors", + "mode": "published", + "name": "basic.paint_colors_trino", + "primary_key": ["color_id", "color_name"], + }, + ), + ( + "/nodes/dimension/", + { + "query": """ + SELECT + id AS mural_id, + color_id, + color_name color_name + FROM + ( + select + id, + colors + from basic.murals + ) murals + LATERAL VIEW EXPLODE(colors) AS color_id, color_name + """, + "description": "Mural paint colors", + "mode": "published", + "name": "basic.paint_colors_spark", + "primary_key": ["color_id", "color_name"], + }, + ), + ( + "/nodes/metric/", + { + "query": """ + SELECT AVG(luminosity) FROM basic.corrected_patches + """, + "description": "Average luminosity of color patch", + "mode": "published", + "name": "basic.avg_luminosity_patches", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "dateint", "type": "int"}, + {"name": "month", "type": "int"}, + {"name": "year", "type": "int"}, + {"name": "day", "type": "int"}, + ], + "description": "Date table", + "mode": "published", + "name": "default.date", + "catalog": "default", + "schema_": "examples", + "table": "date", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "country_code", "type": "string"}, + {"name": "name", "type": "string"}, + {"name": "formation_date", "type": "int"}, + {"name": "last_election_date", "type": "int"}, + ], + "description": "Countries table", + "mode": "published", + "name": "default.countries", + "catalog": "default", + "schema_": "examples", + "table": "countries", + }, + ), + ( + "/nodes/source/", + { + "columns": [ + {"name": "user_id", "type": "int"}, + {"name": "birth_country", "type": "string"}, + {"name": "residence_country", "type": "string"}, + {"name": "age", "type": "int"}, + ], + "description": "Users table", + "mode": "published", + "name": "default.users", + "catalog": "default", + "schema_": "examples", + "table": "users", + }, + ), + ( + "/nodes/dimension/", + { + "description": "Date dimension", + "query": """ + SELECT + dateint, + month, + year, + day + FROM default.date + """, + "mode": "published", + "name": "default.date_dim", + "primary_key": ["dateint"], + }, + ), + ( + "/nodes/dimension/", + { + "description": "Country dimension", + "query": """ + SELECT + country_code, + name, + formation_date, + last_election_date + FROM default.countries + """, + "mode": "published", + "name": "default.special_country_dim", + "primary_key": ["country_code"], + }, + ), + ( + "/nodes/dimension/", + { + "description": "User dimension", + "query": """ + SELECT + user_id, + birth_country, + residence_country, + age + FROM default.users + """, + "mode": "published", + "name": "default.user_dim", + "primary_key": ["user_id"], + }, + ), + ( + "/nodes/metric/", + { + "description": "Average User Age", + "query": """ + SELECT + AVG(age) + FROM default.user_dim + """, + "mode": "published", + "name": "default.avg_user_age", + }, + ), + ( + "/nodes/default.user_dim/columns/birth_country/" + "?dimension=default.special_country_dim&dimension_column=country_code", + {}, + ), + ( + "/nodes/default.user_dim/columns/residence_country/" + "?dimension=default.special_country_dim&dimension_column=country_code", + {}, + ), + ( + "/nodes/default.special_country_dim/columns/formation_date/" + "?dimension=default.date_dim&dimension_column=dateint", + {}, + ), + ( + "/nodes/default.special_country_dim/columns/last_election_date/" + "?dimension=default.date_dim&dimension_column=dateint", + {}, + ), +) + + +COLUMN_MAPPINGS = { + "public.basic.comments": [ + Column(name="id", type=IntegerType()), + Column(name="user_id", type=IntegerType()), + Column(name="timestamp", type=TimestampType()), + Column(name="text", type=StringType()), + ], + "default.roads.repair_orders": [ + Column(name="repair_order_id", type=IntegerType()), + Column(name="municipality_id", type=StringType()), + Column(name="hard_hat_id", type=IntegerType()), + Column(name="order_date", type=TimestampType()), + Column(name="required_date", type=TimestampType()), + Column(name="dispatched_date", type=TimestampType()), + Column(name="dispatcher_id", type=IntegerType()), + Column(name="rating", type=IntegerType()), + ], +} + +QUERY_DATA_MAPPINGS = { + ( + "WITHm0_default_DOT_num_repair_ordersAS(SELECTdefault_DOT_dispatcher.company_name,\t" + "count(default_DOT_repair_orders.repair_order_id)default_DOT_num_repair_ordersFROM" + "roads.repair_ordersASdefault_DOT_repair_ordersLEFTOUTERJOIN(SELECTdefault_DOT_repair" + "_orders.dispatcher_id,\tdefault_DOT_repair_orders.hard_hat_id,\tdefault_DOT_repair_" + "orders.municipality_id,\tdefault_DOT_repair_orders.repair_order_idFROMroads.repair_" + "ordersASdefault_DOT_repair_orders)ASdefault_DOT_repair_orderONdefault_DOT_repair_" + "orders.repair_order_id=default_DOT_repair_order.repair_order_idLEFTOUTERJOIN(SELECT" + "default_DOT_dispatchers.company_name,\tdefault_DOT_dispatchers.dispatcher_idFROMroads" + ".dispatchersASdefault_DOT_dispatchers)ASdefault_DOT_dispatcherONdefault_DOT_repair_order" + ".dispatcher_id=default_DOT_dispatcher.dispatcher_idGROUPBYdefault_DOT_dispatcher." + "company_name),m1_default_DOT_avg_repair_priceAS(SELECTdefault_DOT_dispatcher.company_" + "name,\tavg(default_DOT_repair_order_details.price)ASdefault_DOT_avg_repair_priceFROM" + "roads.repair_order_detailsASdefault_DOT_repair_order_detailsLEFTOUTERJOIN(SELECTdefault" + "_DOT_repair_orders.dispatcher_id,\tdefault_DOT_repair_orders.hard_hat_id,\tdefault_DOT" + "_repair_orders.municipality_id,\tdefault_DOT_repair_orders.repair_order_idFROMroads." + "repair_ordersASdefault_DOT_repair_orders)ASdefault_DOT_repair_orderONdefault_DOT_repair" + "_order_details.repair_order_id=default_DOT_repair_order.repair_order_idLEFTOUTERJOIN(" + "SELECTdefault_DOT_dispatchers.company_name,\tdefault_DOT_dispatchers.dispatcher_idFROM" + "roads.dispatchersASdefault_DOT_dispatchers)ASdefault_DOT_dispatcherONdefault_DOT_repair" + "_order.dispatcher_id=default_DOT_dispatcher.dispatcher_idGROUPBYdefault_DOT_dispatcher." + "company_name)SELECTm0_default_DOT_num_repair_orders.default_DOT_num_repair_orders,\tm1_" + "default_DOT_avg_repair_price.default_DOT_avg_repair_price,\tCOALESCE(m0_default_DOT_num_" + "repair_orders.company_name,m1_default_DOT_avg_repair_price.company_name)company_nameFROM" + "m0_default_DOT_num_repair_ordersFULLOUTERJOINm1_default_DOT_avg_repair_priceONm0_default_" + "DOT_num_repair_orders.company_name=m1_default_DOT_avg_repair_price.company_nameLIMIT10" + ) + .strip() + .replace('"', "") + .replace("\n", "") + .replace("\t", "") + .replace(" ", ""): QueryWithResults( + **{ + "id": "bd98d6be-e2d2-413e-94c7-96d9411ddee2", + "submitted_query": ( + "SELECT avg(repair_order_details.price) AS " + "default_DOT_avg_repair_price,\\n\\tdispatcher.company_name," + "\\n\\tcount(repair_orders.repair_order_id) AS default_DOT_num_repair_orders" + "default_DOT_num_repair_orders \\n FROM roads.repair_order_details AS " + "repair_order_details LEFT OUTER JOIN (SELECT " + "repair_orders.dispatcher_id,\\n\\trepair_orders.hard_hat_id,\\n\\t" + "repair_orders.municipality_id,\\n\\trepair_orders.repair_order_id " + "\\n FROM roads.repair_orders AS repair_orders) AS repair_order ON " + "repair_order_details.repair_order_id = repair_order.repair_order_id\\nLEFT " + "OUTER JOIN (SELECT dispatchers.company_name,\\n\\tdispatchers.dispatcher_id " + "\\n FROM roads.dispatchers AS dispatchers) AS dispatcher ON " + "repair_order.dispatcher_id = dispatcher.dispatcher_id \\n GROUP BY " + "dispatcher.company_name\\nLIMIT 10" + ), + "state": QueryState.FINISHED, + "results": [ + { + "columns": [ + {"name": "default_DOT_num_repair_orders", "type": "int"}, + {"name": "default_DOT_avg_repair_price", "type": "float"}, + {"name": "company_name", "type": "str"}, + ], + "rows": [ + (1.0, "Foo", 100), + (2.0, "Bar", 200), + ], + "sql": "", + }, + ], + "errors": [], + } + ), + ( + "SELECT default_DOT_payment_type_table.id,\n\t" + "default_DOT_payment_type_table.payment_type_classification,\n\t" + "default_DOT_payment_type_table.payment_type_name \n FROM " + "accounting.payment_type_table AS default_DOT_payment_type_table" + ) + .strip() + .replace('"', "") + .replace("\n", "") + .replace("\t", "") + .replace(" ", ""): QueryWithResults( + **{ + "id": "0cb5478c-fd7d-4159-a414-68c50f4b9914", + "submitted_query": ( + "SELECT payment_type_table.id,\n\tpayment_type_table." + "payment_type_classification,\n\t" + 'payment_type_table.payment_type_name \n FROM "accounting"."payment_type_table" ' + "AS payment_type_table" + ), + "state": QueryState.FINISHED, + "results": [ + { + "columns": [ + {"name": "id", "type": "int"}, + {"name": "payment_type_classification", "type": "string"}, + {"name": "payment_type_name", "type": "string"}, + ], + "rows": [ + (1, "CARD", "VISA"), + (2, "CARD", "MASTERCARD"), + ], + "sql": "", + }, + ], + "errors": [], + } + ), + ( + "SELECT COUNT(1) basic_DOT_num_comments \n FROM " + '"basic"."comments" AS basic_DOT_source_DOT_comments' + ) + .strip() + .replace('"', "") + .replace("\n", "") + .replace("\t", "") + .replace(" ", ""): QueryWithResults( + **{ + "id": "ee41ea6c-2303-4fe1-8bf0-f0ce3d6a35ca", + "submitted_query": ( + 'SELECT COUNT(1) basic_DOT_num_comments \n FROM "basic"."comments" ' + "AS basic_DOT_source_DOT_comments" + ), + "state": QueryState.FINISHED, + "results": [ + { + "columns": [{"name": "cnt", "type": "long"}], + "rows": [ + (1,), + ], + "sql": "", + }, + ], + "errors": [], + } + ), + 'SELECT * \n FROM "accounting"."revenue"'.strip() + .replace('"', "") + .replace("\n", "") + .replace("\t", "") + .replace(" ", ""): QueryWithResults( + **{ + "id": "8a8bb03a-74c8-448a-8630-e9439bd5a01b", + "submitted_query": ('SELECT * \n FROM "accounting"."revenue"'), + "state": QueryState.FINISHED, + "results": [ + { + "columns": [{"name": "profit", "type": "float"}], + "rows": [ + (129.19,), + ], + "sql": "", + }, + ], + "errors": [], + } + ), + ( + "SELECT default_DOT_revenue.account_type,\n\tdefault_DOT_revenue.customer_id," + "\n\tdefault_DOT_revenue.payment_amount," + '\n\tdefault_DOT_revenue.payment_id \n FROM "accounting"."revenue" ' + "AS default_DOT_revenue\n \n WHERE default_DOT_revenue.payment_amount " + "> 1000000" + ) + .strip() + .replace('"', "") + .replace("\n", "") + .replace("\t", "") + .replace(" ", ""): QueryWithResults( + **{ + "id": "1b049fb1-652e-458a-ba9d-3669412b34bd", + "submitted_query": ( + "SELECT revenue.account_type,\n\trevenue.customer_id,\n\trevenue.payment_amount," + '\n\trevenue.payment_id \n FROM "accounting"."revenue" AS revenue\n \n ' + "WHERE revenue.payment_amount > 1000000" + ), + "state": QueryState.FINISHED, + "results": [ + { + "columns": [ + {"name": "account_type", "type": "string"}, + {"name": "customer_id", "type": "int"}, + {"name": "payment_amount", "type": "string"}, + {"name": "payment_id", "type": "int"}, + ], + "rows": [ + ("CHECKING", 2, "22.50", 1), + ("SAVINGS", 2, "100.50", 1), + ("CREDIT", 1, "11.50", 1), + ("CHECKING", 2, "2.50", 1), + ], + "sql": "", + }, + ], + "errors": [], + } + ), + ( + """ + WITH +node_query_0 AS (SELECT default_DOT_hard_hats.address, + default_DOT_hard_hats.birth_date, + default_DOT_hard_hats.city, + default_DOT_hard_hats.contractor_id, + default_DOT_hard_hats.country, + default_DOT_hard_hats.first_name, + default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.hire_date, + default_DOT_hard_hats.last_name, + default_DOT_hard_hats.manager, + default_DOT_hard_hats.postal_code, + default_DOT_hard_hats.state, + default_DOT_hard_hats.title + FROM roads.hard_hats AS default_DOT_hard_hats + +) + +SELECT node_query_0.country, + node_query_0.city + FROM node_query_0 + """ + ) + .strip() + .replace('"', "") + .replace("\n", "") + .replace("\t", "") + .replace(" ", ""): QueryWithResults( + **{ + "id": "23cc6e9e-0c55-4fcf-b7e6-cbd2bf126326", + "submitted_query": """WITH\nnode_query_0 AS + (SELECT default_DOT_hard_hats.address,\n + \tdefault_DOT_hard_hats.birth_date,\n + \tdefault_DOT_hard_hats.city,\n + \tdefault_DOT_hard_hats.contractor_id,\n + \tdefault_DOT_hard_hats.country,\n + \tdefault_DOT_hard_hats.first_name,\n + \tdefault_DOT_hard_hats.hard_hat_id,\n + \tdefault_DOT_hard_hats.hire_date,\n + \tdefault_DOT_hard_hats.last_name,\n + \tdefault_DOT_hard_hats.manager,\n + \tdefault_DOT_hard_hats.postal_code,\n + \tdefault_DOT_hard_hats.state,\n + \tdefault_DOT_hard_hats.title \n + FROM roads.hard_hats AS default_DOT_hard_hats + \n\n)\n\nSELECT node_query_0.country, + \n\tnode_query_0.city \n + FROM node_query_0\n\n""", + "state": "FINISHED", + "results": [ + { + "sql": """WITH\nnode_query_0 AS + (SELECT default_DOT_hard_hats.address,\n + \tdefault_DOT_hard_hats.birth_date,\n + \tdefault_DOT_hard_hats.city,\n + \tdefault_DOT_hard_hats.contractor_id,\n + \tdefault_DOT_hard_hats.country,\n + \tdefault_DOT_hard_hats.first_name,\n + \tdefault_DOT_hard_hats.hard_hat_id,\n + \tdefault_DOT_hard_hats.hire_date,\n + \tdefault_DOT_hard_hats.last_name,\n + \tdefault_DOT_hard_hats.manager,\n + \tdefault_DOT_hard_hats.postal_code,\n + \tdefault_DOT_hard_hats.state,\n + \tdefault_DOT_hard_hats.title \n + FROM roads.hard_hats AS default_DOT_hard_hats + \n\n)\n\nSELECT node_query_0.country, + \n\tnode_query_0.city \n + FROM node_query_0\n\n""", + "columns": [], + "rows": [ + ["USA", "Jersey City"], + ["USA", "Middletown"], + ["USA", "Billerica"], + ["USA", "Southampton"], + ["USA", "Southgate"], + ["USA", "Powder Springs"], + ["USA", "Niagara Falls"], + ["USA", "Phoenix"], + ["USA", "Muskogee"], + ], + }, + ], + "errors": [], + } + ), + ( + """ +WITH +metric_query_0 AS (SELECT m0_default_DOT_avg_repair_price.default_DOT_avg_repair_price, + m0_default_DOT_avg_repair_price.city, + m0_default_DOT_avg_repair_price.country + FROM (SELECT default_DOT_hard_hat.city, + default_DOT_hard_hat.country, + avg(default_DOT_repair_order_details.price) AS default_DOT_avg_repair_price + FROM roads.repair_order_details AS default_DOT_repair_order_details + LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders) + AS default_DOT_repair_order ON + default_DOT_repair_order_details.repair_order_id = default_DOT_repair_order.repair_order_id +LEFT OUTER JOIN (SELECT default_DOT_hard_hats.city, + default_DOT_hard_hats.country, + default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats) + AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + GROUP BY default_DOT_hard_hat.country, default_DOT_hard_hat.city +) AS m0_default_DOT_avg_repair_price +LIMIT 5 +) + +SELECT Sum(avg_repair_price), + city + FROM (SELECT metric_query_0.default_DOT_avg_repair_price AS avg_repair_price, + metric_query_0.country, + metric_query_0.city + FROM metric_query_0 +) + GROUP BY city + """ + ) + .strip() + .replace('"', "") + .replace("\n", "") + .replace("\t", "") + .replace(" ", ""): QueryWithResults( + **{ + "id": "23cc6e9e-0c55-4fcf-b7e6-cbd2bf126326", + "submitted_query": """WITH\n + node_query_0 AS (SELECT default_DOT_hard_hats.address,\n + \tdefault_DOT_hard_hats.birth_date,\n + \tdefault_DOT_hard_hats.city,\n + \tdefault_DOT_hard_hats.contractor_id,\n + \tdefault_DOT_hard_hats.country,\n + \tdefault_DOT_hard_hats.first_name,\n + \tdefault_DOT_hard_hats.hard_hat_id,\n + \tdefault_DOT_hard_hats.hire_date,\n + \tdefault_DOT_hard_hats.last_name,\n + \tdefault_DOT_hard_hats.manager,\n + \tdefault_DOT_hard_hats.postal_code,\n + \tdefault_DOT_hard_hats.state,\n + \tdefault_DOT_hard_hats.title \n + FROM roads.hard_hats AS default_DOT_hard_hats\n + \n)\n\nSELECT node_query_0.country,\n + \tnode_query_0.city + \n FROM node_query_0\n\n""", + "state": "FINISHED", + "results": [ + { + "sql": """WITH\nmetric_query_0 AS + (SELECT m0_default_DOT_avg_repair_price.default_DOT_avg_repair_price,\n + \tm0_default_DOT_avg_repair_price.city,\n + \tm0_default_DOT_avg_repair_price.country \n FROM (SELECT default_DOT_hard_hat.city,\n + \tdefault_DOT_hard_hat.country,\n + \tavg(default_DOT_repair_order_details.price) default_DOT_avg_repair_price + \n FROM roads.repair_order_details AS default_DOT_repair_order_details + LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id,\n + \tdefault_DOT_repair_orders.hard_hat_id,\n + \tdefault_DOT_repair_orders.municipality_id,\n + \tdefault_DOT_repair_orders.repair_order_id + \n FROM roads.repair_orders AS default_DOT_repair_orders) + \n AS default_DOT_repair_order ON + default_DOT_repair_order_details.repair_order_id = + default_DOT_repair_order.repair_order_id\n + LEFT OUTER JOIN (SELECT default_DOT_hard_hats.birth_date,\n + \tdefault_DOT_hard_hats.city,\n + \tdefault_DOT_hard_hats.country,\n + \tdefault_DOT_hard_hats.hard_hat_id,\n + \tdefault_DOT_hard_hats.hire_date,\n + \tdefault_DOT_hard_hats.state + \n FROM roads.hard_hats AS default_DOT_hard_hats) + \n AS default_DOT_hard_hat ON + default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + \n GROUP BY default_DOT_hard_hat.country, default_DOT_hard_hat.city + \n) AS m0_default_DOT_avg_repair_price + \nLIMIT 5\n)\n\nSELECT Sum(avg_repair_price),\n + \tcity \n FROM + (SELECT metric_query_0.default_DOT_avg_repair_price AS avg_repair_price,\n + \tmetric_query_0.country,\n + \tmetric_query_0.city \n FROM metric_query_0\n) + \n GROUP BY city\n\n""", + "columns": [], + "rows": [ + [54672.75, "Jersey City"], + [76555.33333333333, "Billerica"], + [64190.6, "Southgate"], + [65682.0, "Phoenix"], + [54083.5, "Southampton"], + ], + }, + ], + "errors": [], + } + ), + ( + """ +WITH +metric_query_0 AS (SELECT m0_default_DOT_avg_repair_price.default_DOT_avg_repair_price, + m1_default_DOT_total_repair_cost.default_DOT_total_repair_cost, + COALESCE(m0_default_DOT_avg_repair_price.city, m1_default_DOT_total_repair_cost.city) city, + COALESCE(m0_default_DOT_avg_repair_price.country, m1_default_DOT_total_repair_cost.country) country + FROM (SELECT default_DOT_hard_hat.city, + default_DOT_hard_hat.country, + avg(default_DOT_repair_order_details.price) AS default_DOT_avg_repair_price + FROM roads.repair_order_details AS default_DOT_repair_order_details LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders) + AS default_DOT_repair_order ON default_DOT_repair_order_details.repair_order_id = default_DOT_repair_order.repair_order_id +LEFT OUTER JOIN (SELECT default_DOT_hard_hats.city, + default_DOT_hard_hats.country, + default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats) + AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + GROUP BY default_DOT_hard_hat.country, default_DOT_hard_hat.city +) AS m0_default_DOT_avg_repair_price FULL OUTER JOIN (SELECT default_DOT_hard_hat.city, + default_DOT_hard_hat.country, + sum(default_DOT_repair_order_details.price) default_DOT_total_repair_cost + FROM roads.repair_order_details AS default_DOT_repair_order_details LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders) + AS default_DOT_repair_order ON default_DOT_repair_order_details.repair_order_id = default_DOT_repair_order.repair_order_id +LEFT OUTER JOIN (SELECT default_DOT_hard_hats.city, + default_DOT_hard_hats.country, + default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats) + AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + GROUP BY default_DOT_hard_hat.country, default_DOT_hard_hat.city +) AS m1_default_DOT_total_repair_cost ON m0_default_DOT_avg_repair_price.city = m1_default_DOT_total_repair_cost.city AND m0_default_DOT_avg_repair_price.country = m1_default_DOT_total_repair_cost.country + +) + +SELECT metric_query_0.default_DOT_avg_repair_price AS avg_repair_price, + metric_query_0.default_DOT_total_repair_cost AS total_cost, + metric_query_0.country, + metric_query_0.city + FROM metric_query_0 + """ + ) + .strip() + .replace('"', "") + .replace("\n", "") + .replace("\t", "") + .replace(" ", ""): QueryWithResults( + **{ + "id": "23cc6e9e-0c55-4fcf-b7e6-cbd2bf126326", + "submitted_query": """WITH +metric_query_0 AS (SELECT m0_default_DOT_avg_repair_price.default_DOT_avg_repair_price, + m1_default_DOT_total_repair_cost.default_DOT_total_repair_cost, + COALESCE(m0_default_DOT_avg_repair_price.city, m1_default_DOT_total_repair_cost.city) city, + COALESCE(m0_default_DOT_avg_repair_price.country, m1_default_DOT_total_repair_cost.country) country + FROM (SELECT default_DOT_hard_hat.city, + default_DOT_hard_hat.country, + avg(default_DOT_repair_order_details.price) AS default_DOT_avg_repair_price + FROM roads.repair_order_details AS default_DOT_repair_order_details LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders) + AS default_DOT_repair_order ON default_DOT_repair_order_details.repair_order_id = default_DOT_repair_order.repair_order_id +LEFT OUTER JOIN (SELECT default_DOT_hard_hats.city, + default_DOT_hard_hats.country, + default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats) + AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + GROUP BY default_DOT_hard_hat.country, default_DOT_hard_hat.city +) AS m0_default_DOT_avg_repair_price FULL OUTER JOIN (SELECT default_DOT_hard_hat.city, + default_DOT_hard_hat.country, + sum(default_DOT_repair_order_details.price) default_DOT_total_repair_cost + FROM roads.repair_order_details AS default_DOT_repair_order_details LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders) + AS default_DOT_repair_order ON default_DOT_repair_order_details.repair_order_id = default_DOT_repair_order.repair_order_id +LEFT OUTER JOIN (SELECT default_DOT_hard_hats.city, + default_DOT_hard_hats.country, + default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats) + AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + GROUP BY default_DOT_hard_hat.country, default_DOT_hard_hat.city +) AS m1_default_DOT_total_repair_cost ON m0_default_DOT_avg_repair_price.city = m1_default_DOT_total_repair_cost.city AND m0_default_DOT_avg_repair_price.country = m1_default_DOT_total_repair_cost.country + +) + +SELECT metric_query_0.default_DOT_avg_repair_price AS avg_repair_price, + metric_query_0.default_DOT_total_repair_cost AS total_cost, + metric_query_0.country, + metric_query_0.city + FROM metric_query_0""", + "state": "FINISHED", + "results": [ + { + "sql": """WITH +metric_query_0 AS (SELECT m0_default_DOT_avg_repair_price.default_DOT_avg_repair_price, + m1_default_DOT_total_repair_cost.default_DOT_total_repair_cost, + COALESCE(m0_default_DOT_avg_repair_price.city, m1_default_DOT_total_repair_cost.city) city, + COALESCE(m0_default_DOT_avg_repair_price.country, m1_default_DOT_total_repair_cost.country) country + FROM (SELECT default_DOT_hard_hat.city, + default_DOT_hard_hat.country, + avg(default_DOT_repair_order_details.price) AS default_DOT_avg_repair_price + FROM roads.repair_order_details AS default_DOT_repair_order_details LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders) + AS default_DOT_repair_order ON default_DOT_repair_order_details.repair_order_id = default_DOT_repair_order.repair_order_id +LEFT OUTER JOIN (SELECT default_DOT_hard_hats.city, + default_DOT_hard_hats.country, + default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats) + AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + GROUP BY default_DOT_hard_hat.country, default_DOT_hard_hat.city +) AS m0_default_DOT_avg_repair_price FULL OUTER JOIN (SELECT default_DOT_hard_hat.city, + default_DOT_hard_hat.country, + sum(default_DOT_repair_order_details.price) default_DOT_total_repair_cost + FROM roads.repair_order_details AS default_DOT_repair_order_details LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id, + default_DOT_repair_orders.hard_hat_id, + default_DOT_repair_orders.municipality_id, + default_DOT_repair_orders.repair_order_id + FROM roads.repair_orders AS default_DOT_repair_orders) + AS default_DOT_repair_order ON default_DOT_repair_order_details.repair_order_id = default_DOT_repair_order.repair_order_id +LEFT OUTER JOIN (SELECT default_DOT_hard_hats.city, + default_DOT_hard_hats.country, + default_DOT_hard_hats.hard_hat_id, + default_DOT_hard_hats.state + FROM roads.hard_hats AS default_DOT_hard_hats) + AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id + GROUP BY default_DOT_hard_hat.country, default_DOT_hard_hat.city +) AS m1_default_DOT_total_repair_cost ON m0_default_DOT_avg_repair_price.city = m1_default_DOT_total_repair_cost.city AND m0_default_DOT_avg_repair_price.country = m1_default_DOT_total_repair_cost.country + +) + +SELECT metric_query_0.default_DOT_avg_repair_price AS avg_repair_price, + metric_query_0.default_DOT_total_repair_cost AS total_cost, + metric_query_0.country, + metric_query_0.city + FROM metric_query_0""", + "columns": [], + "rows": [ + [54672.75, 218691.0, "USA", "Jersey City"], + [76555.33333333333, 229666.0, "USA", "Billerica"], + [64190.6, 320953.0, "USA", "Southgate"], + [65682.0, 131364.0, "USA", "Phoenix"], + [54083.5, 216334.0, "USA", "Southampton"], + [65595.66666666667, 196787.0, "USA", "Powder Springs"], + [39301.5, 78603.0, "USA", "Middletown"], + [70418.0, 70418.0, "USA", "Muskogee"], + [53374.0, 53374.0, "USA", "Niagara Falls"], + ], + }, + ], + "errors": [], + } + ), +} diff --git a/datajunction-server/tests/fixes_test.py b/datajunction-server/tests/fixes_test.py new file mode 100644 index 000000000..5006d5f02 --- /dev/null +++ b/datajunction-server/tests/fixes_test.py @@ -0,0 +1,3 @@ +""" +Tests for ``dj.fixes``. +""" diff --git a/datajunction-server/tests/integration/__init__.py b/datajunction-server/tests/integration/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/datajunction-server/tests/integration/basic_test.py b/datajunction-server/tests/integration/basic_test.py new file mode 100644 index 000000000..8a3a7e809 --- /dev/null +++ b/datajunction-server/tests/integration/basic_test.py @@ -0,0 +1,23 @@ +""" +Basic integration tests. +""" + +import pytest +from sqlalchemy.engine import create_engine + + +@pytest.mark.integration_test +def test_query() -> None: + """ + Test a simple query. + """ + engine = create_engine("dj://localhost:8000/0") + connection = engine.connect() + + sql = """ +SELECT "core.users.gender", "core.num_comments" +FROM metrics +GROUP BY "core.users.gender" + """ + results = list(connection.execute(sql)) + assert results == [("female", 5), ("non-binary", 10), ("male", 7)] diff --git a/datajunction-server/tests/internal/authentication/basic_test.py b/datajunction-server/tests/internal/authentication/basic_test.py new file mode 100644 index 000000000..6a96b4aa2 --- /dev/null +++ b/datajunction-server/tests/internal/authentication/basic_test.py @@ -0,0 +1,103 @@ +""" +Tests for basic auth helper functions +""" +import asyncio +from unittest.mock import MagicMock + +import pytest +from fastapi.testclient import TestClient +from requests import Request +from sqlmodel import Session + +from datajunction_server.errors import DJException +from datajunction_server.internal.authentication import basic + + +def test_hash_and_verify_password(): + """ + Test hashing a password and verifying a password against a hash + """ + hashed_password = basic.get_password_hash(password="foo") + assert basic.verify_password(plain_password="foo", hashed_password=hashed_password) + + +def test_get_user_info(client: TestClient, session: Session): + """ + Test getting user info given a username and a password + """ + client.post("/basic/user/", data={"username": "dj", "password": "dj"}) + user = basic.get_user_info(username="dj", password="dj", session=session) + assert user.username == "dj" + + +def test_fail_invalid_credentials(client: TestClient, session: Session): + """ + Test failing on invalid user credentials + """ + client.post("/basic/user/", data={"username": "dj", "password": "incorrect"}) + with pytest.raises(DJException) as exc_info: + basic.get_user_info(username="dj", password="dj", session=session) + assert "Invalid username or password" in str(exc_info.value) + + +def test_fail_on_user_already_exists(client: TestClient): + """ + Test failing when creating a user that already exists + """ + client.post("/basic/user/", data={"username": "dj", "password": "dj"}) + response = client.post("/basic/user/", data={"username": "dj", "password": "dj"}) + assert response.status_code == 409 + assert response.json() == { + "message": "User dj already exists.", + "errors": [ + { + "code": 2, + "message": "User dj already exists.", + "debug": None, + "context": "", + }, + ], + "warnings": [], + } + + +def test_parse_basic_auth_cookie(client: TestClient, session: Session): + """ + Test parsing a basic auth cookie + """ + response = client.post("/basic/user/", data={"username": "dj", "password": "dj"}) + assert response.ok + response = client.post("/basic/login/", data={"username": "dj", "password": "dj"}) + assert response.ok + request = Request("GET", "/metrics/", cookies=response.cookies) + request.url = MagicMock() + request.state = MagicMock() # type: ignore + asyncio.run(basic.parse_basic_auth_cookie(request=request, session=session)) + assert request.state.user.username == "dj" # type: ignore + + +def test_failing_parse_basic_auth_cookie(session: Session): + """ + Test failing on parsing a basic auth cookie + """ + request = Request("GET", "/metrics/", cookies={"__dj": "foo"}) + request.url = MagicMock() + with pytest.raises(DJException) as exc_info: + asyncio.run(basic.parse_basic_auth_cookie(request=request, session=session)) + assert "This endpoint requires authentication." in str(exc_info.value) + + +def test_basic_whoami(client: TestClient): + """ + Test the /basic/whoami/ endpoint + """ + response = client.get("/basic/whoami/") + assert response.ok + assert response.json() == { + "id": None, + "username": "dj", + "email": None, + "name": None, + "oauth_provider": "basic", + "is_admin": False, + } diff --git a/datajunction-server/tests/internal/authentication/github_test.py b/datajunction-server/tests/internal/authentication/github_test.py new file mode 100644 index 000000000..df55dac95 --- /dev/null +++ b/datajunction-server/tests/internal/authentication/github_test.py @@ -0,0 +1,15 @@ +""" +Tests for GitHub OAuth helper functions +""" +from datajunction_server.internal.authentication import github + + +def test_get_authorize_url(): + """ + Test generating a GitHub OAuth authorize url for a GitHub app client ID + """ + assert github.get_authorize_url("foo") == ( + "https://github.com/login/oauth/authorize?" + "client_id=foo&scope=read:user&redirect_uri=" + "http://localhost:8000/github/token/" + ) diff --git a/datajunction-server/tests/internal/authentication/jwt_test.py b/datajunction-server/tests/internal/authentication/jwt_test.py new file mode 100644 index 000000000..ac7029768 --- /dev/null +++ b/datajunction-server/tests/internal/authentication/jwt_test.py @@ -0,0 +1,29 @@ +""" +Test JWT helper functions +""" +from datetime import timedelta + +from requests import Request + +from datajunction_server.internal.authentication import jwt + + +def test_create_and_get_jwt(): + """ + Test creating a JWT and getting it back from a request + """ + jwt_string = jwt.create_jwt( + data={"foo": "bar"}, + expires_delta=timedelta(minutes=30), + ) + request = Request("GET", "/metrics/", cookies={"__dj": jwt_string}) + data = jwt.get_jwt(request=request) + assert data["foo"] == "bar" + + +def test_encrypt_and_decrypt(): + """ + Test encrypting and decrypting a value + """ + encrypted_string = jwt.encrypt("foo") + assert jwt.decrypt(encrypted_string) == "foo" diff --git a/datajunction-server/tests/migrations_test.py b/datajunction-server/tests/migrations_test.py new file mode 100644 index 000000000..8677496c9 --- /dev/null +++ b/datajunction-server/tests/migrations_test.py @@ -0,0 +1,55 @@ +"""Verify alembic migrations.""" +import pytest +from sqlalchemy import create_engine +from sqlalchemy.engine.base import Connection +from sqlalchemy.pool import StaticPool +from sqlmodel import SQLModel + +from alembic.autogenerate import compare_metadata +from alembic.config import Config +from alembic.runtime.environment import EnvironmentContext +from alembic.runtime.migration import MigrationContext +from alembic.script import ScriptDirectory + + +@pytest.fixture(scope="function", name="connection") +def connection_fixture() -> Connection: + """ + Create an in-memory SQLite connection for verifying models. + """ + engine = create_engine( + "sqlite://", + connect_args={"check_same_thread": False}, + poolclass=StaticPool, + ) + with engine.connect() as conn: + transaction = conn.begin() + yield conn + transaction.rollback() + + +def test_migrations_are_current(connection): + """ + Verify that the alembic migrations are in line with the models. + """ + target_metadata = SQLModel.metadata + + config = Config("alembic.ini") + config.set_main_option("script_location", "alembic") + script = ScriptDirectory.from_config(config) + + context = EnvironmentContext( + config, + script, + fn=lambda rev, _: script._upgrade_revs("head", rev), # pylint: disable=W0212 + ) + context.configure(connection=connection) + context.run_migrations() + + # Don't use compare_type due to false positives. + migrations_state = MigrationContext.configure( + connection, + opts={"compare_type": False}, + ) + diff = compare_metadata(migrations_state, target_metadata) + assert diff == [], "The alembic migrations do not match the models." diff --git a/datajunction-server/tests/models/__init__.py b/datajunction-server/tests/models/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/datajunction-server/tests/models/catalog_test.py b/datajunction-server/tests/models/catalog_test.py new file mode 100644 index 000000000..443c6da3f --- /dev/null +++ b/datajunction-server/tests/models/catalog_test.py @@ -0,0 +1,20 @@ +""" +Tests for ``datajunction_server.models.catalog``. +""" + +from datajunction_server.models.catalog import Catalog + + +def test_catalog_str_and_hash(): + """ + Test that a catalog instance works properly with str and hash + """ + spark = Catalog(id=1, name="spark") + assert str(spark) == "spark" + assert hash(spark) == 1 + trino = Catalog(id=2, name="trino") + assert str(trino) == "trino" + assert hash(trino) == 2 + druid = Catalog(id=3, name="druid") + assert str(druid) == "druid" + assert hash(druid) == 3 diff --git a/datajunction-server/tests/models/hash_test.py b/datajunction-server/tests/models/hash_test.py new file mode 100644 index 000000000..588171767 --- /dev/null +++ b/datajunction-server/tests/models/hash_test.py @@ -0,0 +1,22 @@ +""" +Tests for ``datajunction_server.models.database``. +""" + +import datajunction_server.sql.parsing.types as ct +from datajunction_server.models.column import Column +from datajunction_server.models.database import Database +from datajunction_server.models.table import Table + + +def test_hash() -> None: + """ + Test the hash method to compare models. + """ + database = Database(id=1, name="test", URI="sqlite://") + assert database in {database} + + table = Table(id=1, database=database, table="table") + assert table in {table} + + column = Column(id=1, name="test", type=ct.IntegerType(), table=table) + assert column in {column} diff --git a/datajunction-server/tests/models/materialization_test.py b/datajunction-server/tests/models/materialization_test.py new file mode 100644 index 000000000..d62ee1d9e --- /dev/null +++ b/datajunction-server/tests/models/materialization_test.py @@ -0,0 +1,53 @@ +"""Tests materialization-related models.""" +from datajunction_server.models.materialization import ( + GenericMaterializationConfig, + Partition, +) + + +def test_generic_materialization_config(): + """ + Test that the generic materialization config's identifier generation only + takes into account categorical values when generating the hash for partition + values. + """ + mat_config = GenericMaterializationConfig( + query="SELECT 1", + partitions=[ + Partition( + name="date_int", + type_="temporal", + range=[20200101, 20200601], + ), + Partition( + name="country", + type_="categorical", + range=["MY"], + ), + ], + ) + assert mat_config.identifier() == "date_int_country_1634115696" + + mat_config = GenericMaterializationConfig( + query="SELECT 1", + partitions=[ + Partition( + name="date_int", + type_="temporal", + range=[20200101, 20200601], + ), + ], + ) + assert mat_config.identifier() == "date_int_0" + + mat_config = GenericMaterializationConfig( + query="SELECT 1", + partitions=[ + Partition( + name="country", + type_="categorical", + range=["MY"], + ), + ], + ) + assert mat_config.identifier() == "country_1634115696" diff --git a/datajunction-server/tests/models/node_test.py b/datajunction-server/tests/models/node_test.py new file mode 100644 index 000000000..c60ae3b77 --- /dev/null +++ b/datajunction-server/tests/models/node_test.py @@ -0,0 +1,139 @@ +""" +Tests for ``datajunction_server.models.node``. +""" + +# pylint: disable=use-implicit-booleaness-not-comparison + +import pytest +from sqlmodel import Session + +from datajunction_server.models.node import Node, NodeRevision, NodeType + + +def test_node_relationship(session: Session) -> None: + """ + Test the n:n self-referential relationships. + """ + node_a = Node(name="A", current_version="1") + node_a_rev = NodeRevision(name="A", version="1", node=node_a) + + node_b = Node(name="B", current_version="1") + node_a_rev = NodeRevision(name="B", version="1", node=node_b) + + node_c = Node(name="C", current_version="1") + node_c_rev = NodeRevision( + name="C", + version="1", + node=node_c, + parents=[node_a, node_b], + ) + + session.add(node_c_rev) + + assert node_a.children == [node_c_rev] + assert node_b.children == [node_c_rev] + assert node_c.children == [] + + assert node_a_rev.parents == [] + assert node_a_rev.parents == [] + assert node_c_rev.parents == [node_a, node_b] + + +def test_extra_validation() -> None: + """ + Test ``extra_validation``. + """ + node = Node(name="A", type=NodeType.SOURCE, current_version="1") + node_revision = NodeRevision( + name=node.name, + type=node.type, + node=node, + version="1", + query="SELECT * FROM B", + ) + with pytest.raises(Exception) as excinfo: + node_revision.extra_validation() + assert str(excinfo.value) == "Node A of type source should not have a query" + + node = Node(name="A", type=NodeType.METRIC, current_version="1") + node_revision = NodeRevision( + name=node.name, + type=node.type, + node=node, + version="1", + ) + with pytest.raises(Exception) as excinfo: + node_revision.extra_validation() + assert str(excinfo.value) == "Node A of type metric needs a query" + + node = Node(name="A", type=NodeType.METRIC, current_version="1") + node_revision = NodeRevision( + name=node.name, + type=node.type, + node=node, + version="1", + query="SELECT count(repair_order_id) " + "AS Anum_repair_orders " + "FROM repair_orders", + ) + node_revision.extra_validation() + + node = Node(name="A", type=NodeType.METRIC, current_version="1") + node_revision = NodeRevision( + name=node.name, + type=node.type, + node=node, + version="1", + query="SELECT count(repair_order_id) + " + "repair_order_id AS Anum_repair_orders " + "FROM repair_orders", + ) + with pytest.raises(Exception) as excinfo: + node_revision.extra_validation() + assert str(excinfo.value) == ( + "Metric A has an invalid query, should have a single aggregation" + ) + + node = Node(name="A", type=NodeType.TRANSFORM, current_version="1") + node_revision = NodeRevision( + name=node.name, + type=node.type, + node=node, + version="1", + query="SELECT * FROM B", + ) + node_revision.extra_validation() + + node = Node(name="A", type=NodeType.TRANSFORM, current_version="1") + node_revision = NodeRevision( + name=node.name, + type=node.type, + node=node, + version="1", + ) + with pytest.raises(Exception) as excinfo: + node_revision.extra_validation() + assert str(excinfo.value) == "Node A of type transform needs a query" + + node = Node(name="A", type=NodeType.CUBE, current_version="1") + node_revision = NodeRevision(name=node.name, type=node.type, node=node, version="1") + with pytest.raises(Exception) as excinfo: + node_revision.extra_validation() + assert str(excinfo.value) == "Node A of type cube node needs cube elements" + + node = Node(name="A", type=NodeType.TRANSFORM, current_version="1") + node_revision = NodeRevision( + name=node.name, + type=node.type, + node=node, + version="1", + query="SELECT * FROM B", + required_dimensions=["B.x"], + ) + with pytest.raises(Exception) as excinfo: + node_revision.extra_validation() + + assert str(excinfo.value) == ( + "Node A of type transform cannot have " + "bound dimensions which are only for metrics." + ) diff --git a/datajunction-server/tests/models/query_test.py b/datajunction-server/tests/models/query_test.py new file mode 100644 index 000000000..6fec6852f --- /dev/null +++ b/datajunction-server/tests/models/query_test.py @@ -0,0 +1,93 @@ +""" +Tests for the query model. +""" + +from datetime import datetime + +import msgpack + +from datajunction_server.models.query import ( + ColumnMetadata, + QueryResults, + QueryWithResults, + StatementResults, + decode_results, + encode_results, +) +from datajunction_server.typing import QueryState + + +def test_msgpack() -> None: + """ + Test the msgpack encoding/decoding + """ + query_with_results = QueryWithResults( + catalog=None, + schema=None, + id="5599b970-23f0-449b-baea-c87a2735423b", + submitted_query="SELECT 42 AS answer", + executed_query="SELECT 42 AS answer", + scheduled=datetime(2021, 1, 1), + started=datetime(2021, 1, 2), + finished=datetime(2021, 1, 3), + state=QueryState.FINISHED, + progress=1, + output_table=None, + results=QueryResults( + __root__=[ + StatementResults( + sql="SELECT 42 AS answer", + columns=[ColumnMetadata(name="answer", type="int")], + rows=[(42,)], + row_count=1, + ), + ], + ), + next=None, + previous=None, + errors=[], + ) + encoded = msgpack.packb( + query_with_results.dict(by_alias=True), + default=encode_results, + ) + decoded = msgpack.unpackb(encoded, ext_hook=decode_results) + assert decoded == { + "id": "5599b970-23f0-449b-baea-c87a2735423b", + "submitted_query": "SELECT 42 AS answer", + "executed_query": "SELECT 42 AS answer", + "engine_name": None, + "engine_version": None, + "output_table": None, + "scheduled": datetime(2021, 1, 1, 0, 0), + "started": datetime(2021, 1, 2, 0, 0), + "finished": datetime(2021, 1, 3, 0, 0), + "progress": 1.0, + "state": "FINISHED", + "results": [ + { + "sql": "SELECT 42 AS answer", + "columns": [{"name": "answer", "type": "int"}], + "rows": [[42]], + "row_count": 1, + }, + ], + "next": None, + "previous": None, + "errors": [], + "links": None, + } + + +def test_encode_results_unknown() -> None: + """ + Test that ``encode_results`` passes through unknown objects. + """ + assert encode_results(1) == 1 + + +def test_decode_results_unknown() -> None: + """ + Test that ``decode_results`` passes through unknown objects. + """ + assert decode_results(42, b"packed") == msgpack.ExtType(42, b"packed") diff --git a/datajunction-server/tests/service_clients_test.py b/datajunction-server/tests/service_clients_test.py new file mode 100644 index 000000000..22db35cb4 --- /dev/null +++ b/datajunction-server/tests/service_clients_test.py @@ -0,0 +1,436 @@ +""" +Tests for ``datajunction_server.service_clients``. +""" +from unittest.mock import MagicMock + +import pytest +from pytest_mock import MockerFixture +from requests import Request + +from datajunction_server.errors import DJQueryServiceClientException +from datajunction_server.models import Engine +from datajunction_server.models.materialization import GenericMaterializationInput +from datajunction_server.models.node import NodeType +from datajunction_server.models.query import QueryCreate +from datajunction_server.service_clients import ( + QueryServiceClient, + RequestsSessionWithEndpoint, +) + + +class TestRequestsSessionWithEndpoint: + """ + Test using requests session with endpoint. + """ + + example_endpoint = "http://pieservice:7020" + + @pytest.fixture + def requests_session(self) -> RequestsSessionWithEndpoint: + """ + Create a requests session. + """ + return RequestsSessionWithEndpoint(endpoint=self.example_endpoint) + + def test_prepare_request( + self, + requests_session: RequestsSessionWithEndpoint, + ) -> None: + """ + Test preparing request + """ + req = Request( + "GET", + f"{self.example_endpoint}/pies/?flavor=blueberry", + data=None, + headers=None, + ) + prepped = requests_session.prepare_request(req) + assert prepped.headers["Connection"] == "keep-alive" + + def test_make_requests( + self, + mocker: MockerFixture, + requests_session: RequestsSessionWithEndpoint, + ): + """ + Test making requests. + """ + mock_request = mocker.patch("requests.Session.request") + + requests_session.get("/pies/") + mock_request.assert_called_with( + "GET", + f"{self.example_endpoint}/pies/", + allow_redirects=True, + ) + + requests_session.post("/pies/", json={"flavor": "blueberry", "diameter": 10}) + mock_request.assert_called_with( + "POST", + f"{self.example_endpoint}/pies/", + data=None, + json={"flavor": "blueberry", "diameter": 10}, + ) + + +class TestQueryServiceClient: # pylint: disable=too-few-public-methods + """ + Test using the query service client. + """ + + endpoint = "http://queryservice:8001" + + def test_query_service_client_get_columns_for_table( + self, + mocker: MockerFixture, + ) -> None: + """ + Test the query service client. + """ + + mock_request = mocker.patch("requests.Session.request") + query_service_client = QueryServiceClient(uri=self.endpoint) + query_service_client.get_columns_for_table("hive", "test", "pies") + mock_request.assert_called_with( + "GET", + "http://queryservice:8001/table/hive.test.pies/columns/", + params={}, + allow_redirects=True, + ) + + query_service_client.get_columns_for_table( + "hive", + "test", + "pies", + engine=Engine(name="spark", version="2.4.4"), + ) + mock_request.assert_called_with( + "GET", + "http://queryservice:8001/table/hive.test.pies/columns/", + params={"engine": "spark", "engine_version": "2.4.4"}, + allow_redirects=True, + ) + + def test_query_service_client_submit_query(self, mocker: MockerFixture) -> None: + """ + Test submitting a query to a query service client. + """ + + mock_response = MagicMock() + mock_response.ok = True + mock_response.json.return_value = { + "catalog_name": "public", + "engine_name": "postgres", + "engine_version": "15.2", + "id": "ef209eef-c31a-4089-aae6-833259a08e22", + "submitted_query": "SELECT 1 as num", + "executed_query": "SELECT 1 as num", + "scheduled": "2023-01-01T00:00:00.000000", + "started": "2023-01-01T00:00:00.000000", + "finished": "2023-01-01T00:00:00.000001", + "state": "FINISHED", + "progress": 1, + "results": [ + { + "sql": "SELECT 1 as num", + "columns": [{"name": "num", "type": "STR"}], + "rows": [[1]], + "row_count": 1, + }, + ], + "next": None, + "previous": None, + "errors": [], + } + + mock_request = mocker.patch( + "datajunction_server.service_clients.RequestsSessionWithEndpoint.post", + return_value=mock_response, + ) + + query_service_client = QueryServiceClient(uri=self.endpoint) + query_create = QueryCreate( + catalog_name="default", + engine_name="postgres", + engine_version="15.2", + submitted_query="SELECT 1", + async_=False, + ) + query_service_client.submit_query(query_create) + + mock_request.assert_called_with( + "/queries/", + json={ + "catalog_name": "default", + "engine_name": "postgres", + "engine_version": "15.2", + "submitted_query": "SELECT 1", + "async_": False, + }, + ) + + def test_query_service_client_get_query(self, mocker: MockerFixture) -> None: + """ + Test getting a previously submitted query from a query service client. + """ + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = { + "catalog_name": "public", + "engine_name": "postgres", + "engine_version": "15.2", + "id": "ef209eef-c31a-4089-aae6-833259a08e22", + "submitted_query": "SELECT 1 as num", + "executed_query": "SELECT 1 as num", + "scheduled": "2023-01-01T00:00:00.000000", + "started": "2023-01-01T00:00:00.000000", + "finished": "2023-01-01T00:00:00.000001", + "state": "FINISHED", + "progress": 1, + "results": [ + { + "sql": "SELECT 1 as num", + "columns": [{"name": "num", "type": "STR"}], + "rows": [[1]], + "row_count": 1, + }, + ], + "next": None, + "previous": None, + "errors": [], + "database_id": 1, # Will be deprecated soon in favor of catalog + } + + mock_request = mocker.patch( + "datajunction_server.service_clients.RequestsSessionWithEndpoint.get", + return_value=mock_response, + ) + + query_service_client = QueryServiceClient(uri=self.endpoint) + query_service_client.get_query( + "ef209eef-c31a-4089-aae6-833259a08e22", + ) + + mock_request.assert_called_with( + "/queries/ef209eef-c31a-4089-aae6-833259a08e22/", + ) + + def test_query_service_client_materialize(self, mocker: MockerFixture) -> None: + """ + Test materialize from a query service client. + """ + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = { + "urls": ["http://fake.url/job"], + "output_tables": ["common.a", "common.b"], + } + + mock_request = mocker.patch( + "datajunction_server.service_clients.RequestsSessionWithEndpoint.post", + return_value=mock_response, + ) + + query_service_client = QueryServiceClient(uri=self.endpoint) + query_service_client.materialize( + GenericMaterializationInput( + name="default", + node_name="default.hard_hat", + node_version="v1", + node_type=NodeType.DIMENSION, + schedule="0 * * * *", + query="", + spark_conf={}, + upstream_tables=["default.hard_hats"], + partitions=[], + ), + ) + + mock_request.assert_called_with( + "/materialization/", + json={ + "name": "default", + "node_name": "default.hard_hat", + "node_version": "v1", + "node_type": "dimension", + "partitions": [], + "query": "", + "schedule": "0 * * * *", + "spark_conf": {}, + "upstream_tables": ["default.hard_hats"], + }, + ) + + def test_query_service_client_deactivate_materialization( + self, + mocker: MockerFixture, + ) -> None: + """ + Test deactivate materialization from a query service client. + """ + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = { + "urls": ["http://fake.url/job"], + "output_tables": ["common.a", "common.b"], + } + + mock_request = mocker.patch( + "datajunction_server.service_clients.RequestsSessionWithEndpoint.delete", + return_value=mock_response, + ) + + query_service_client = QueryServiceClient(uri=self.endpoint) + query_service_client.deactivate_materialization( + node_name="default.hard_hat", + materialization_name="default", + ) + + mock_request.assert_called_with( + "/materialization/", + params={ + "node_name": "default.hard_hat", + "materialization_name": "default", + }, + ) + + def test_query_service_client_raising_error(self, mocker: MockerFixture) -> None: + """ + Test handling an error response from the query service client + """ + mock_response = MagicMock() + mock_response.ok = False + + mocker.patch( + "datajunction_server.service_clients.RequestsSessionWithEndpoint.get", + return_value=mock_response, + ) + mocker.patch( + "datajunction_server.service_clients.RequestsSessionWithEndpoint.post", + return_value=mock_response, + ) + + query_service_client = QueryServiceClient(uri=self.endpoint) + + with pytest.raises(DJQueryServiceClientException) as exc_info: + query_service_client.get_query( + "ef209eef-c31a-4089-aae6-833259a08e22", + ) + assert "Error response from query service" in str(exc_info.value) + query_create = QueryCreate( + catalog_name="hive", + engine_name="postgres", + engine_version="15.2", + submitted_query="SELECT 1", + async_=False, + ) + + with pytest.raises(DJQueryServiceClientException) as exc_info: + query_service_client.submit_query(query_create) + assert "Error response from query service" in str(exc_info.value) + + def test_materialize(self, mocker: MockerFixture) -> None: + """ + Test get materialization urls for a given node materialization + """ + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = { + "urls": ["http://fake.url/job"], + "output_tables": ["common.a", "common.b"], + } + + mock_request = mocker.patch( + "datajunction_server.service_clients.RequestsSessionWithEndpoint.post", + return_value=mock_response, + ) + + query_service_client = QueryServiceClient(uri=self.endpoint) + response = query_service_client.materialize( + GenericMaterializationInput( + name="default", + node_name="default.hard_hat", + node_version="v1", + node_type=NodeType.DIMENSION, + schedule="0 * * * *", + query="", + spark_conf={}, + upstream_tables=["default.hard_hats"], + partitions=[], + ), + ) + mock_request.assert_called_with( + "/materialization/", + json={ + "name": "default", + "node_name": "default.hard_hat", + "node_version": "v1", + "node_type": "dimension", + "schedule": "0 * * * *", + "query": "", + "upstream_tables": ["default.hard_hats"], + "spark_conf": {}, + "partitions": [], + }, + ) + assert response == { + "urls": ["http://fake.url/job"], + "output_tables": ["common.a", "common.b"], + } + + def test_get_materialization_info(self, mocker: MockerFixture) -> None: + """ + Test get materialization urls for a given node materialization + """ + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = { + "urls": ["http://fake.url/job"], + "output_tables": ["common.a", "common.b"], + } + + mock_request = mocker.patch( + "datajunction_server.service_clients.RequestsSessionWithEndpoint.get", + return_value=mock_response, + ) + + query_service_client = QueryServiceClient(uri=self.endpoint) + response = query_service_client.get_materialization_info( + node_name="default.hard_hat", + node_version="v3.1", + materialization_name="default", + ) + mock_request.assert_called_with( + "/materialization/default.hard_hat/v3.1/default/", + timeout=3, + ) + assert response == { + "urls": ["http://fake.url/job"], + "output_tables": ["common.a", "common.b"], + } + + def test_get_materialization_info_error(self, mocker: MockerFixture) -> None: + """ + Test get materialization info with errors + """ + mock_response = MagicMock() + mock_response.status_code = 500 + mock_response.ok = False + mock_response.json.return_value = {"message": "An error has occurred"} + + mocker.patch( + "datajunction_server.service_clients.RequestsSessionWithEndpoint.get", + return_value=mock_response, + ) + + query_service_client = QueryServiceClient(uri=self.endpoint) + response = query_service_client.get_materialization_info( + node_name="default.hard_hat", + node_version="v3.1", + materialization_name="default", + ) + assert response == { + "urls": [], + "output_tables": [], + } diff --git a/datajunction-server/tests/sql/__init__.py b/datajunction-server/tests/sql/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/datajunction-server/tests/sql/dag_test.py b/datajunction-server/tests/sql/dag_test.py new file mode 100644 index 000000000..f98982f99 --- /dev/null +++ b/datajunction-server/tests/sql/dag_test.py @@ -0,0 +1,75 @@ +""" +Tests for ``datajunction_server.sql.dag``. +""" + +from datajunction_server.models.column import Column +from datajunction_server.models.database import Database +from datajunction_server.models.node import Node, NodeRevision, NodeType +from datajunction_server.models.table import Table +from datajunction_server.sql.dag import get_dimensions +from datajunction_server.sql.parsing.types import IntegerType, StringType + + +def test_get_dimensions() -> None: + """ + Test ``get_dimensions``. + """ + database = Database(id=1, name="one", URI="sqlite://") + + dimension_ref = Node(name="B", type=NodeType.DIMENSION, current_version="1") + dimension = NodeRevision( + node=dimension_ref, + version="1", + tables=[ + Table( + database=database, + table="B", + columns=[ + Column(name="id", type=IntegerType()), + Column(name="attribute", type=StringType()), + ], + ), + ], + columns=[ + Column(name="id", type=IntegerType()), + Column(name="attribute", type=StringType()), + ], + ) + dimension_ref.current = dimension + + parent_ref = Node(name="A", current_version="1") + parent = NodeRevision( + node=parent_ref, + version="1", + tables=[ + Table( + database=database, + table="A", + columns=[ + Column(name="ds", type=StringType()), + Column(name="b_id", type=IntegerType(), dimension=dimension_ref), + ], + ), + ], + columns=[ + Column(name="ds", type=StringType()), + Column(name="b_id", type=IntegerType(), dimension=dimension_ref), + ], + ) + parent_ref.current = parent + + child_ref = Node(name="C", current_version="1", type=NodeType.METRIC) + child = NodeRevision( + node=child_ref, + version="1", + query="SELECT COUNT(*) FROM A", + parents=[parent_ref], + type=NodeType.METRIC, + ) + child_ref.current = child + + assert get_dimensions(child_ref) == [ + {"name": "A.b_id", "type": "int", "path": []}, + {"name": "B.attribute", "type": "string", "path": ["b_id"]}, + {"name": "B.id", "type": "int", "path": ["b_id"]}, + ] diff --git a/datajunction-server/tests/sql/functions_test.py b/datajunction-server/tests/sql/functions_test.py new file mode 100644 index 000000000..2e3d66d9e --- /dev/null +++ b/datajunction-server/tests/sql/functions_test.py @@ -0,0 +1,2814 @@ +# pylint: disable=line-too-long,too-many-lines +""" +Tests for ``datajunction_server.sql.functions``. +""" + +import pytest +from sqlmodel import Session + +import datajunction_server.sql.functions as F +import datajunction_server.sql.parsing.types as ct +from datajunction_server.errors import DJException, DJNotImplementedException +from datajunction_server.sql.functions import ( + Avg, + Coalesce, + Count, + Max, + Min, + Now, + Sum, + ToDate, + function_registry, +) +from datajunction_server.sql.parsing import ast +from datajunction_server.sql.parsing.backends.antlr4 import parse +from datajunction_server.sql.parsing.backends.exceptions import DJParseException +from datajunction_server.sql.parsing.types import ( + BigIntType, + DateType, + DecimalType, + DoubleType, + FloatType, + IntegerType, + NullType, + StringType, + WildcardType, +) + + +def test_missing_functions() -> None: + """ + Test missing functions. + """ + with pytest.raises(DJNotImplementedException) as excinfo: + function_registry["INVALID_FUNCTION"] # pylint: disable=pointless-statement + assert ( + str(excinfo.value) == "The function `INVALID_FUNCTION` hasn't been implemented " + "in DJ yet. You can file an issue at https://github.com/" + "DataJunction/dj/issues/new?title=Function+missing:+" + "INVALID_FUNCTION to request it to be added, or use the " + "documentation at https://github.com/DataJunction/dj/blob" + "/main/docs/functions.rst to implement it." + ) + + +def test_bad_combo_types() -> None: + """ + Tests dispatch raises on bad types + """ + with pytest.raises(TypeError) as exc: + Avg.infer_type(ast.Column(ast.Name("x"), _type=StringType())) + assert "got an invalid combination of types" in str(exc) + + +def test_abs(session: Session): + """ + Test the `abs` Spark function + """ + query = parse( + """ + select abs(-1) + """, + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.IntegerType() # type: ignore + + query = parse( + """ + select abs(-1.1) + """, + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.FloatType() # type: ignore + + +def test_aggregate(session: Session): + """ + Test the `aggregate` Spark function + """ + query = parse( + """ + select + aggregate(items, '', (acc, x) -> (case + when acc = '' then element_at(split(x, '::'), 1) + when acc = 'a' then acc + else element_at(split(x, '::'), 1) end)) as item + from ( + select 1 as id, ARRAY('b', 'c', 'a', 'x', 'g', 'z') AS items + ) + """, + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert query.select.projection[0].type == StringType() # type: ignore + + +def test_approx_percentile(session: Session): + """ + Test the `approx_percentile` Spark function + """ + query_with_list = parse("SELECT approx_percentile(10.0, array(0.5, 0.4, 0.1), 100)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query_with_list.compile(ctx) + assert not exc.errors + assert query_with_list.select.projection[0].type == ct.ListType(element_type=ct.FloatType()) # type: ignore + + query_with_list = parse("SELECT approx_percentile(10.0, 0.5, 100)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query_with_list.compile(ctx) + assert not exc.errors + assert query_with_list.select.projection[0].type == ct.FloatType() # type: ignore + + +def test_array(session: Session): + """ + Test the `array` Spark function + """ + query = parse( + """ + SELECT array() FROM (select 1 as col) + """, + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.ListType(element_type=ct.NullType()) # type: ignore + + query = parse( + """ + SELECT array(1, 2, 3) FROM (select 1 as col) + """, + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.ListType(element_type=ct.IntegerType()) # type: ignore + + +def test_array_agg(session: Session): + """ + Test the `array_agg` Spark function + """ + query = parse( + """ + SELECT array_agg(col) FROM (select 1 as col) + """, + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.ListType(element_type=ct.IntegerType()) # type: ignore + + query = parse( + """ + SELECT array_agg(col) FROM (select 'foo' as col) + """, + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.ListType(element_type=ct.StringType()) # type: ignore + + +def test_array_append(session: Session): + """ + Test the `array_append` Spark function + """ + query = parse("SELECT array_append(array('b', 'd', 'c', 'a'), 'd')") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.ListType(element_type=ct.StringType()) # type: ignore + + query = parse("SELECT array_append(array(1, 2, 3, 4), 5)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.ListType(element_type=ct.IntegerType()) # type: ignore + + query = parse("SELECT array_append(array(true, false, true, true), false)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.ListType(element_type=ct.BooleanType()) # type: ignore + + +def test_array_compact(session: Session): + """ + Test the `array_compact` Spark function + """ + query = parse( + 'SELECT array_compact(array(1, 2, 3, null)), array_compact(array("a", "b", "c"))', + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.ListType(element_type=ct.IntegerType()) # type: ignore + assert query.select.projection[1].type == ct.ListType(element_type=ct.StringType()) # type: ignore + + +def test_array_contains(session: Session): + """ + Test the `array_contains` Spark function + """ + query = parse("select array_contains(array(1, 2, 3), 2)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.BooleanType() # type: ignore + + +def test_array_distinct(session: Session): + """ + Test the `array_distinct` Spark function + """ + query = parse( + """ + SELECT array_distinct(array(1, 2, 3, 3)) + """, + ) + ctx = ast.CompileContext(session=session, exception=DJException()) + query.compile(ctx) + assert query.select.projection[0].type == ct.ListType(element_type=ct.IntegerType()) # type: ignore + + query = parse( + """ + SELECT array_distinct(array('a', 'b', 'b', 'z')) + """, + ) + ctx = ast.CompileContext(session=session, exception=DJException()) + query.compile(ctx) + assert query.select.projection[0].type == ct.ListType(element_type=ct.StringType()) # type: ignore + + +def test_array_except(session: Session): + """ + Test the `array_except` Spark function + """ + query = parse( + """ + SELECT array_except(array(1, 2, 3), array(1, 3, 5)) + """, + ) + ctx = ast.CompileContext(session=session, exception=DJException()) + query.compile(ctx) + assert query.select.projection[0].type == ct.ListType(element_type=ct.IntegerType()) # type: ignore + + query = parse( + """ + SELECT array_except(array('a', 'b', 'b', 'z'), array('a', 'b')) + """, + ) + ctx = ast.CompileContext(session=session, exception=DJException()) + query.compile(ctx) + assert query.select.projection[0].type == ct.ListType(element_type=ct.StringType()) # type: ignore + + +def test_array_intersect(session: Session): + """ + Test the `array_intersect` Spark function + """ + query = parse( + """ + SELECT array_intersect(array(1, 2, 3), array(1, 3, 5)) + """, + ) + ctx = ast.CompileContext(session=session, exception=DJException()) + query.compile(ctx) + assert query.select.projection[0].type == ct.ListType(element_type=ct.IntegerType()) # type: ignore + + query = parse( + """ + SELECT array_intersect(array('a', 'b', 'b', 'z'), array('a', 'b')) + """, + ) + ctx = ast.CompileContext(session=session, exception=DJException()) + query.compile(ctx) + assert query.select.projection[0].type == ct.ListType(element_type=ct.StringType()) # type: ignore + + +def test_array_join(session: Session): + """ + Test the `array_join` Spark function + """ + query = parse( + """ + SELECT array_join(array('hello', 'world'), ' ') + """, + ) + ctx = ast.CompileContext(session=session, exception=DJException()) + query.compile(ctx) + assert query.select.projection[0].type == ct.StringType() # type: ignore + + query = parse( + """ + SELECT array_join(array('hello', null ,'world'), ' ', ',') + """, + ) + ctx = ast.CompileContext(session=session, exception=DJException()) + query.compile(ctx) + assert query.select.projection[0].type == ct.StringType() # type: ignore + + +def test_array_max(session: Session): + """ + Test the `array_max` Spark function + """ + query = parse( + """ + SELECT array_max(array(1, 20, null, 3)) + """, + ) + ctx = ast.CompileContext(session=session, exception=DJException()) + query.compile(ctx) + assert query.select.projection[0].type == ct.IntegerType() # type: ignore + + +def test_array_min(session: Session): + """ + Test the `array_min` Spark function + """ + query = parse( + """ + SELECT array_min(array(1.0, 202.2, null, 3.333)) + """, + ) + ctx = ast.CompileContext(session=session, exception=DJException()) + query.compile(ctx) + assert query.select.projection[0].type == ct.FloatType() # type: ignore + + +def test_array_position(session: Session): + """ + Test the `array_position` function + """ + query = parse( + """ + SELECT array_position(array(1.0, 202.2, null, 3.333), 1.0) + """, + ) + ctx = ast.CompileContext(session=session, exception=DJException()) + query.compile(ctx) + assert query.select.projection[0].type == ct.LongType() # type: ignore + + +def test_array_remove(session: Session): + """ + Test the `array_remove` function + """ + query = parse( + """ + SELECT array_remove(array(1.0, 202.2, null, 3.333), 1.0) + """, + ) + ctx = ast.CompileContext(session=session, exception=DJException()) + query.compile(ctx) + assert query.select.projection[0].type == ct.ListType(element_type=ct.FloatType()) # type: ignore + + +def test_array_repeat(session: Session): + """ + Test the `array_repeat` function + """ + query = parse( + """ + SELECT array_repeat('abc', 10), array_repeat(100, 10), array_repeat(1.23, 10) + """, + ) + ctx = ast.CompileContext(session=session, exception=DJException()) + query.compile(ctx) + assert query.select.projection[0].type == ct.ListType(element_type=ct.StringType()) # type: ignore + assert query.select.projection[1].type == ct.ListType(element_type=ct.IntegerType()) # type: ignore + assert query.select.projection[2].type == ct.ListType(element_type=ct.FloatType()) # type: ignore + + +def test_array_size(session: Session): + """ + Test the `array_size` function + """ + query = parse( + """ + SELECT array_size(array('abc', 'd', 'e', 'f')) + """, + ) + ctx = ast.CompileContext(session=session, exception=DJException()) + query.compile(ctx) + assert query.select.projection[0].type == ct.LongType() # type: ignore + + +def test_array_sort(session: Session): + """ + Test the `array_sort` function + """ + query = parse( + """ + SELECT + array_sort(array('b', 'd', null, 'c', 'a')) + """, + ) + ctx = ast.CompileContext(session=session, exception=DJException()) + query.compile(ctx) + assert query.select.projection[0].type == ct.ListType( # type: ignore + element_type=ct.StringType(), + ) + + +def test_array_union(session: Session): + """ + Test the `array_union` function + """ + query = parse( + """ + SELECT array_union(array('b', 'd', null), array('c', 'a')) + """, + ) + ctx = ast.CompileContext(session=session, exception=DJException()) + query.compile(ctx) + assert query.select.projection[0].type == ct.ListType( # type: ignore + element_type=ct.StringType(), + ) + + +def test_array_overlap(session: Session): + """ + Test the `array_overlap` function + """ + query = parse( + """ + SELECT arrays_overlap(array(1, 2, 3), array(3, 4, 5)) + """, + ) + ctx = ast.CompileContext(session=session, exception=DJException()) + query.compile(ctx) + assert query.select.projection[0].type == ct.BooleanType() # type: ignore + + +def test_avg() -> None: + """ + Test ``avg`` function. + """ + assert ( + Avg.infer_type(ast.Column(ast.Name("x"), _type=IntegerType())) == DoubleType() + ) + assert Avg.infer_type(ast.Column(ast.Name("x"), _type=FloatType())) == DoubleType() + + +def test_cardinality(session: Session): + """ + Test the `cardinality` Spark function + """ + query_with_list = parse("SELECT cardinality(array('b', 'd', 'c', 'a'))") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query_with_list.compile(ctx) + assert not exc.errors + assert query_with_list.select.projection[0].type == ct.IntegerType() # type: ignore + + query_with_map = parse("SELECT cardinality(map('a', 1, 'b', 2))") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query_with_map.compile(ctx) + assert not exc.errors + assert query_with_map.select.projection[0].type == ct.IntegerType() # type: ignore + + +def test_cbrt_func(session: Session): + """ + Test the `cbrt` function + """ + query = parse("SELECT cbrt(27), cbrt(64.0)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == FloatType() # type: ignore + assert query.select.projection[1].type == FloatType() # type: ignore + + +def test_char_func(session: Session): + """ + Test the `char` function + """ + query = parse("SELECT char(65), char(97)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == StringType() # type: ignore + assert query.select.projection[1].type == StringType() # type: ignore + + +def test_char_length_func(session: Session): + """ + Test the `char_length` function + """ + query = parse("SELECT char_length('hello'), char_length('world')") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == IntegerType() # type: ignore + assert query.select.projection[1].type == IntegerType() # type: ignore + + +def test_character_length_func(session: Session): + """ + Test the `character_length` function + """ + query = parse("SELECT character_length('hello'), character_length('world')") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == IntegerType() # type: ignore + assert query.select.projection[1].type == IntegerType() # type: ignore + + +def test_chr_func(session: Session): + """ + Test the `chr` function + """ + query = parse("SELECT chr(65), chr(97)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == StringType() # type: ignore + assert query.select.projection[1].type == StringType() # type: ignore + + +@pytest.mark.parametrize( + "types, expected", + [ + ((ct.IntegerType(),), ct.BigIntType()), + ((ct.FloatType(),), ct.BigIntType()), + ((ct.DoubleType(),), ct.BigIntType()), + ((ct.TinyIntType(), ct.IntegerType()), ct.DecimalType(precision=3, scale=0)), + ((ct.SmallIntType(), ct.IntegerType()), ct.DecimalType(precision=5, scale=0)), + ((ct.IntegerType(), ct.IntegerType()), ct.DecimalType(precision=10, scale=0)), + ((ct.BigIntType(), ct.IntegerType()), ct.DecimalType(precision=20, scale=0)), + ((ct.DoubleType(), ct.IntegerType()), ct.DecimalType(precision=30, scale=0)), + ((ct.FloatType(), ct.IntegerType()), ct.DecimalType(precision=14, scale=0)), + ( + (ct.DecimalType(10, 2), ct.IntegerType()), + ct.DecimalType(precision=9, scale=0), + ), + ( + (ct.DecimalType(precision=9, scale=0),), + ct.DecimalType(precision=10, scale=0), + ), + ], +) +def test_ceil(types, expected) -> None: + """ + Test ``ceil`` function. + """ + if len(types) == 1: + assert F.Ceil.infer_type(ast.Column(ast.Name("x"), _type=types[0])) == expected + else: + assert ( + F.Ceil.infer_type( + *( + ast.Column(ast.Name("x"), _type=types[0]), + ast.Number(0, _type=types[1]), + ) + ) + == expected + ) + + +def test_ceil_ceiling_funcs(session: Session): + """ + Test the `ceil` and `ceiling` functions + """ + query = parse( + "SELECT ceil(-0.1), ceil(5), ceil(3.1411, 3), ceil(3.1411, -3), " + "ceiling(-0.1), ceiling(5), ceiling(3.1411, 3), ceiling(3.1411, -3)", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == BigIntType() # type: ignore + assert query.select.projection[1].type == BigIntType() # type: ignore + assert query.select.projection[2].type == DecimalType(precision=14, scale=3) # type: ignore + assert query.select.projection[3].type == DecimalType(precision=14, scale=0) # type: ignore + assert query.select.projection[4].type == BigIntType() # type: ignore + assert query.select.projection[5].type == BigIntType() # type: ignore + assert query.select.projection[6].type == DecimalType(precision=14, scale=3) # type: ignore + assert query.select.projection[7].type == DecimalType(precision=14, scale=0) # type: ignore + + +def test_coalesce_infer_type() -> None: + """ + Test type inference in the ``Coalesce`` function. + """ + assert ( + Coalesce.infer_type( + ast.Column(ast.Name("x"), _type=StringType()), + ast.Column(ast.Name("x"), _type=StringType()), + ast.Column(ast.Name("x"), _type=StringType()), + ) + == StringType() + ) + + assert ( + Coalesce.infer_type( + ast.Column(ast.Name("x"), _type=IntegerType()), + ast.Column(ast.Name("x"), _type=NullType()), + ast.Column(ast.Name("x"), _type=BigIntType()), + ) + == IntegerType() + ) + + assert ( + Coalesce.infer_type( + ast.Column(ast.Name("x"), _type=StringType()), + ast.Column(ast.Name("x"), _type=StringType()), + ast.Column(ast.Name("x"), _type=NullType()), + ) + == StringType() + ) + + +def test_concat_func(session: Session): + """ + Test the `concat` function + """ + query = parse( + "SELECT concat('hello', '+', 'world'), concat(array(1, 2), array(3))", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.StringType() # type: ignore + assert query.select.projection[1].type == ct.ListType(ct.IntegerType()) # type: ignore + + +def test_concat_ws_func(session: Session): + """ + Test the `concat_ws` function + """ + query = parse( + "SELECT concat_ws(',', 'hello', 'world'), concat_ws('-', 'spark', 'sql', 'function')", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == StringType() # type: ignore + assert query.select.projection[1].type == StringType() # type: ignore + + +def test_collect_list(session: Session): + """ + Test the `collect_list` function + """ + query = parse("SELECT collect_list(col) FROM (SELECT (1), (2) AS col)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.ListType( # type: ignore + element_type=ct.IntegerType(), + ) + + +def test_collect_set(session: Session): + """ + Test the `collect_set` function + """ + query = parse("SELECT collect_set(col) FROM (SELECT (1), (2) AS col)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.ListType( # type: ignore + element_type=ct.IntegerType(), + ) + + +def test_contains_func(session: Session): + """ + Test the `contains` function + """ + query = parse( + "SELECT contains('hello world', 'world'), contains('hello world', 'spark')", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.BooleanType() # type: ignore + assert query.select.projection[1].type == ct.BooleanType() # type: ignore + + +def test_conv_func(session: Session): + """ + Test the `conv` function + """ + query = parse("SELECT conv('10', 10, 2), conv(15, 10, 16)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.StringType() # type: ignore + assert query.select.projection[1].type == ct.StringType() # type: ignore + + +def test_convert_timezone_func(session: Session): + """ + Test the `convert_timezone` function + """ + query = parse( + "SELECT convert_timezone('PST', 'EST', cast('2023-07-30 12:34:56' as timestamp))", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.TimestampType() # type: ignore + + +def test_corr_func(session: Session): + """ + Test the `corr` function + """ + query = parse("SELECT corr(2.0, 3.0), corr(5, 10)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.FloatType() # type: ignore + assert query.select.projection[1].type == ct.FloatType() # type: ignore + + +def test_cos_func(session: Session): + """ + Test the `cos` function + """ + query = parse("SELECT cos(0), cos(3.1416)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.FloatType() # type: ignore + assert query.select.projection[1].type == ct.FloatType() # type: ignore + + +def test_cosh_func(session: Session): + """ + Test the `cosh` function + """ + query = parse("SELECT cosh(0), cosh(1.0)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.FloatType() # type: ignore + assert query.select.projection[1].type == ct.FloatType() # type: ignore + + +def test_cot_func(session: Session): + """ + Test the `cot` function + """ + query = parse("SELECT cot(1), cot(0.7854)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.FloatType() # type: ignore + assert query.select.projection[1].type == ct.FloatType() # type: ignore + + +def test_count() -> None: + """ + Test ``Count`` function. + """ + assert ( + Count.infer_type(ast.Column(ast.Name("x"), _type=WildcardType())) + == BigIntType() + ) + assert Count.is_aggregation is True + + +def test_count_if_func(session: Session): + """ + Test the `count_if` function + """ + query = parse("SELECT count_if(true), count_if(false)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.IntegerType() # type: ignore + assert query.select.projection[1].type == ct.IntegerType() # type: ignore + + +def test_count_min_sketch(session: Session): + """ + Test the `count_min_sketch` function + """ + query = parse( + "SELECT count_min_sketch(col, 0.5, 0.5, 1) FROM (SELECT (1), (2), (1) AS col)", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.BinaryType() # type: ignore + + +def test_covar_pop_func(session: Session): + """ + Test the `covar_pop` function + """ + query = parse("SELECT covar_pop(1.0, 2.0), covar_pop(3, 4)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.FloatType() # type: ignore + assert query.select.projection[1].type == ct.FloatType() # type: ignore + + +def test_covar_samp_func(session: Session): + """ + Test the `covar_samp` function + """ + query = parse("SELECT covar_samp(1.0, 2.0), covar_samp(3, 4)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.FloatType() # type: ignore + assert query.select.projection[1].type == ct.FloatType() # type: ignore + + +def test_crc32_func(session: Session): + """ + Test the `crc32` function + """ + query = parse("SELECT crc32('hello'), crc32('world')") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.BigIntType() # type: ignore + assert query.select.projection[1].type == ct.BigIntType() # type: ignore + + +def test_csc_func(session: Session): + """ + Test the `csc` function + """ + query = parse("SELECT csc(1), csc(0.7854)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.FloatType() # type: ignore + assert query.select.projection[1].type == ct.FloatType() # type: ignore + + +def test_cume_dist_func(session: Session): + """ + Test the `cume_dist` function + """ + query = parse("SELECT cume_dist(), cume_dist()") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.FloatType() # type: ignore + assert query.select.projection[1].type == ct.FloatType() # type: ignore + + +def test_curdate_func(session: Session): + """ + Test the `curdate` function + """ + query = parse("SELECT curdate(), curdate()") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.DateType() # type: ignore + assert query.select.projection[1].type == ct.DateType() # type: ignore + + +def test_current_catalog_func(session: Session): + """ + Test the `current_catalog` function + """ + query = parse("SELECT current_catalog(), current_catalog()") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.StringType() # type: ignore + assert query.select.projection[1].type == ct.StringType() # type: ignore + + +def test_current_database_func(session: Session): + """ + Test the `current_database` function + """ + query = parse("SELECT current_database(), current_database()") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.StringType() # type: ignore + assert query.select.projection[1].type == ct.StringType() # type: ignore + + +def test_current_schema_func(session: Session): + """ + Test the `current_schema` function + """ + query = parse("SELECT current_schema(), current_schema()") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.StringType() # type: ignore + assert query.select.projection[1].type == ct.StringType() # type: ignore + + +def test_current_timezone_current_user_funcs(session: Session): + """ + Test the `current_timezone` function + Test the `current_user` function + """ + query = parse("SELECT current_timezone(), current_user()") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.StringType() # type: ignore + assert query.select.projection[1].type == ct.StringType() # type: ignore + + +def test_date_func(session: Session): + """ + Test the `date` function + """ + query = parse( + "SELECT date('2023-07-30'), date(cast('2023-07-30 12:34:56' as timestamp))", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.DateType() # type: ignore + assert query.select.projection[1].type == ct.DateType() # type: ignore + + +def test_date_from_unix_date_func(session: Session): + """ + Test the `date_from_unix_date` function + """ + query = parse("SELECT date_from_unix_date(0), date_from_unix_date(18500)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.DateType() # type: ignore + assert query.select.projection[1].type == ct.DateType() # type: ignore + + +def test_date_format(session: Session) -> None: + """ + Test ``date_format`` function. + """ + query_with_array = parse("SELECT date_format(NOW(), 'yyyyMMdd') as date_partition") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query_with_array.compile(ctx) + assert not exc.errors + assert query_with_array.select.projection[0].type == StringType() # type: ignore + + +def test_date_part_func(session: Session): + """ + Test the `date_part` function + """ + query = parse( + "SELECT date_part('year', cast('2023-07-30' as date)), date_part('hour', cast('2023-07-30 12:34:56' as timestamp))", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.IntegerType() # type: ignore + assert query.select.projection[1].type == ct.IntegerType() # type: ignore + + +def test_dj_logical_timestamp(session: Session) -> None: + """ + Test ``DJ_LOGICAL_TIMESTAMP`` function. + """ + query_with_array = parse("SELECT dj_logical_timestamp() as date_partition") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query_with_array.compile(ctx) + assert not exc.errors + assert query_with_array.select.projection[0].type == StringType() # type: ignore + + +def test_dayofmonth_func(session: Session): + """ + Test the `dayofmonth` function + """ + query = parse( + "SELECT dayofmonth(cast('2023-07-30' as date)), dayofmonth('2023-01-01')", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.IntegerType() # type: ignore + assert query.select.projection[1].type == ct.IntegerType() # type: ignore + + +def test_dayofweek_func(session: Session): + """ + Test the `dayofweek` function + """ + query = parse( + "SELECT dayofweek(cast('2023-07-30' as date)), dayofweek('2023-01-01')", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.IntegerType() # type: ignore + assert query.select.projection[1].type == ct.IntegerType() # type: ignore + + +def test_dayofyear_func(session: Session): + """ + Test the `dayofyear` function + """ + query = parse( + "SELECT dayofyear(cast('2023-07-30' as date)), dayofyear('2023-01-01')", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.IntegerType() # type: ignore + assert query.select.projection[1].type == ct.IntegerType() # type: ignore + + +def test_decimal_func(session: Session): + """ + Test the `decimal` function + """ + query = parse("SELECT decimal(123), decimal('456.78')") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.DecimalType(8, 6) # type: ignore + assert query.select.projection[1].type == ct.DecimalType(8, 6) # type: ignore + + +def test_decode_func(session: Session): + """ + Test the `decode` function + """ + query = parse("SELECT decode(unhex('4D7953514C'), 'UTF-8')") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.StringType() # type: ignore + + +def test_degrees_func(session: Session): + """ + Test the `degrees` function + """ + query = parse("SELECT degrees(1), degrees(3.141592653589793)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.FloatType() # type: ignore + assert query.select.projection[1].type == ct.FloatType() # type: ignore + + +def test_double_func(session: Session): + """ + Test the `double` function + """ + query = parse("SELECT double('123.45'), double(67890)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.DoubleType() # type: ignore + assert query.select.projection[1].type == ct.DoubleType() # type: ignore + + +def test_e_func(session: Session): + """ + Test the `e` function + """ + query = parse("SELECT e(), e()") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.FloatType() # type: ignore + assert query.select.projection[1].type == ct.FloatType() # type: ignore + + +def test_element_at(session: Session): + """ + Test the `element_at` Spark function + """ + query_with_array = parse("SELECT element_at(array(1, 2, 3, 4), 2)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query_with_array.compile(ctx) + assert not exc.errors + assert query_with_array.select.projection[0].type == IntegerType() # type: ignore + + query_with_map = parse("SELECT element_at(map(1, 'a', 2, 'b'), 2)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query_with_map.compile(ctx) + assert not exc.errors + assert query_with_map.select.projection[0].type == StringType() # type: ignore + + +def test_elt_func(session: Session): + """ + Test the `elt` function + """ + query = parse("SELECT elt(1, 'a', 'b', 'c'), elt(3, 'd', 'e', 'f')") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.StringType() # type: ignore + assert query.select.projection[1].type == ct.StringType() # type: ignore + + +def test_encode_func(session: Session): + """ + Test the `encode` function + """ + query = parse("SELECT encode('hello', 'UTF-8'), encode('world', 'UTF-8')") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.StringType() # type: ignore + assert query.select.projection[1].type == ct.StringType() # type: ignore + + +def test_endswith_func(session: Session): + """ + Test the `endswith` function + """ + query = parse("SELECT endswith('hello', 'lo'), endswith('world', 'ld')") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.BooleanType() # type: ignore + assert query.select.projection[1].type == ct.BooleanType() # type: ignore + + +def test_equal_null_func(session: Session): + """ + Test the `equal_null` function + """ + query = parse("SELECT equal_null('hello', 'hello'), equal_null(NULL, NULL)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.BooleanType() # type: ignore + assert query.select.projection[1].type == ct.BooleanType() # type: ignore + + +def test_every_func(session: Session): + """ + Test the `every` function + """ + query = parse("SELECT every(col), every(col) FROM (SELECT (true), (false) AS col)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.BooleanType() # type: ignore + assert query.select.projection[1].type == ct.BooleanType() # type: ignore + + +def test_exists_func(session: Session): + """ + Test the `exists` function + """ + query = parse("SELECT exists(array(1, 2, 3), x -> x % 2 > 0)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert query.select.projection[0].type == ct.BooleanType() # type: ignore + + +def test_explode_outer_func(session: Session): + """ + Test the `explode_outer` function + """ + query = parse( + "SELECT explode_outer(array(1, 2, 3)), explode_outer(map('key1', 'value1', 'key2', 'value2'))", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.IntegerType() # type: ignore + assert query.select.projection[1].type == ct.StringType() # type: ignore + + +def test_expm1_func(session: Session): + """ + Test the `expm1` function + """ + query = parse("SELECT expm1(1), expm1(0.5)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.FloatType() # type: ignore + assert query.select.projection[1].type == ct.FloatType() # type: ignore + + +def test_factorial_func(session: Session): + """ + Test the `factorial` function + """ + query = parse("SELECT factorial(0), factorial(5)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.IntegerType() # type: ignore + assert query.select.projection[1].type == ct.IntegerType() # type: ignore + + +def test_filter(session: Session): + """ + Test the `filter` function + """ + query = parse("SELECT filter(col, s -> s != 3) FROM (SELECT array(1, 2, 3) AS col)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert query.select.projection[0].type == ct.ListType( # type: ignore + element_type=ct.IntegerType(), + ) + + query = parse( + "SELECT filter(col, (s, i) -> s + i != 3) FROM (SELECT array(1, 2, 3) AS col)", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert query.select.projection[0].type == ct.ListType( # type: ignore + element_type=ct.IntegerType(), + ) + + with pytest.raises(DJParseException): + query = parse( + "SELECT filter(col, (s, i, a) -> s + i != 3) FROM (SELECT array(1, 2, 3) AS col)", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + + +def test_find_in_set_func(session: Session): + """ + Test the `find_in_set` function + """ + query = parse("SELECT find_in_set('b', 'a,b,c,d'), find_in_set('e', 'a,b,c,d')") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.IntegerType() # type: ignore + assert query.select.projection[1].type == ct.IntegerType() # type: ignore + + +def test_first_and_first_value(session: Session): + """ + Test `first` and `first_value` + """ + query = parse( + "SELECT first(col), first(col, true), first_value(col), " + "first_value(col, true) FROM (SELECT (1), (2) AS col)", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.IntegerType() # type: ignore + assert query.select.projection[1].type == ct.IntegerType() # type: ignore + assert query.select.projection[2].type == ct.IntegerType() # type: ignore + assert query.select.projection[3].type == ct.IntegerType() # type: ignore + + +def test_flatten(session: Session): + """ + Test `flatten` + """ + query = parse("SELECT flatten(array(array(1, 2), array(3, 4)))") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.ListType( # type: ignore + element_type=ct.IntegerType(), + ) + + +def test_float_func(session: Session): + """ + Test the `float` function + """ + query = parse("SELECT float(123), float('456.78')") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.FloatType() # type: ignore + assert query.select.projection[1].type == ct.FloatType() # type: ignore + + +@pytest.mark.parametrize( + "types, expected", + [ + ((ct.IntegerType(),), ct.BigIntType()), + ((ct.FloatType(),), ct.BigIntType()), + ((ct.DoubleType(),), ct.BigIntType()), + ((ct.TinyIntType(), ct.IntegerType()), ct.DecimalType(precision=3, scale=0)), + ((ct.SmallIntType(), ct.IntegerType()), ct.DecimalType(precision=5, scale=0)), + ((ct.IntegerType(), ct.IntegerType()), ct.DecimalType(precision=10, scale=0)), + ((ct.BigIntType(), ct.IntegerType()), ct.DecimalType(precision=20, scale=0)), + ((ct.DoubleType(), ct.IntegerType()), ct.DecimalType(precision=30, scale=0)), + ((ct.FloatType(), ct.IntegerType()), ct.DecimalType(precision=14, scale=0)), + ( + (ct.DecimalType(10, 2), ct.IntegerType()), + ct.DecimalType(precision=9, scale=0), + ), + ( + (ct.DecimalType(precision=9, scale=0),), + ct.DecimalType(precision=10, scale=0), + ), + ], +) +def test_floor(types, expected) -> None: + """ + Test ``floor`` function. + """ + if len(types) == 1: + assert F.Floor.infer_type(ast.Column(ast.Name("x"), _type=types[0])) == expected + else: + assert ( + F.Floor.infer_type( + *( + ast.Column(ast.Name("x"), _type=types[0]), + ast.Number(0, _type=types[1]), + ) + ) + == expected + ) + + +def test_forall_func(session: Session): + """ + Test the `forall` function + """ + query = parse( + "SELECT forall(array(1, 2, 3), x -> x > 0), forall(array(1, 2, 3), x -> x < 0)", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + # assert not exc.errors + assert query.select.projection[0].type == ct.BooleanType() # type: ignore + assert query.select.projection[1].type == ct.BooleanType() # type: ignore + + +def test_format_number_func(session: Session): + """ + Test the `format_number` function + """ + query = parse( + "SELECT format_number(12345.6789, 2), format_number(98765.4321, '###.##')", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.StringType() # type: ignore + assert query.select.projection[1].type == ct.StringType() # type: ignore + + +def test_format_string_func(session: Session): + """ + Test the `format_string` function + """ + query = parse( + "SELECT format_string('%s %s', 'hello', 'world'), format_string('%d %d', 1, 2)", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.StringType() # type: ignore + assert query.select.projection[1].type == ct.StringType() # type: ignore + + +# TODO: Fix these two # pylint: disable=fixme +# def test_from_csv_func(session: Session): +# """ +# Test the `from_csv` function +# """ +# query = parse("SELECT from_csv('1,2,3', 'a INT, b INT, c INT'), from_csv('4,5,6', 'x INT, y INT, z INT')") +# exc = DJException() +# ctx = ast.CompileContext(session=session, exception=exc) +# query.compile(ctx) +# assert not exc.errors +# assert isinstance(query.select.projection[0].type, ct.StructType) # type: ignore +# assert isinstance(query.select.projection[1].type, ct.StructType) # type: ignore + + +# TODO: Fix these two # pylint: disable=fixme +# def test_from_json_func(session: Session): +# """ +# Test the `from_json` function +# """ +# query = parse("SELECT from_json('1,2,3', 'a INT, b INT, c INT'), from_json('4,5,6', 'x INT, y INT, z INT')") +# exc = DJException() +# ctx = ast.CompileContext(session=session, exception=exc) +# query.compile(ctx) +# assert not exc.errors +# assert isinstance(query.select.projection[0].type, Union[ct.StructType, ct.ListType]) # type: ignore +# assert isinstance(query.select.projection[1].type, Union[ct.StructType, ct.ListType]) + + +def test_from_unix_time_func(session: Session): + """ + Test the `from_unix_time` function + """ + query = parse( + "SELECT from_unixtime(1609459200, 'yyyy-MM-dd HH:mm:ss'), from_unixtime(1609459200, 'dd/MM/yyyy HH:mm:ss')", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.StringType() # type: ignore + assert query.select.projection[1].type == ct.StringType() # type: ignore + + +def test_from_utc_timestamp_func(session: Session): + """ + Test the `from_utc_timestamp` function + """ + query = parse( + "SELECT from_utc_timestamp('2023-01-01 00:00:00', 'PST'), " + "from_utc_timestamp(cast('2023-01-01 00:00:00' as timestamp), 'IST')", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.TimestampType() # type: ignore + assert query.select.projection[1].type == ct.TimestampType() # type: ignore + + +def test_get_func(session: Session): + """ + Test the `get` function + """ + query = parse("SELECT get(array(1, 2, 3), 0), get(array('a', 'b', 'c'), 1)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.IntegerType() # type: ignore + assert query.select.projection[1].type == ct.StringType() # type: ignore + + +def test_get_json_object_func(session: Session): + """ + Test the `get_json_object` function + """ + query = parse( + "SELECT get_json_object('{\"key\": \"value\"}', '$.key'), " + 'get_json_object(\'{"key1": "value1", "key2": "value2"}\', \'$.key2\')', + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.StringType() # type: ignore + assert query.select.projection[1].type == ct.StringType() # type: ignore + + +def test_getbit_func(session: Session): + """ + Test the `getbit` function + """ + query = parse("SELECT getbit(1010, 0), getbit(1010, 1)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.IntegerType() # type: ignore + assert query.select.projection[1].type == ct.IntegerType() # type: ignore + + +def test_greatest(session: Session): + """ + Test `greatest` + """ + query = parse("SELECT greatest(10, 9, 2, 4, 3)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.IntegerType() # type: ignore + + +def test_grouping_func(session: Session): + """ + Test the `grouping` function + """ + query = parse( + "SELECT grouping(col1), grouping(col2) FROM " + "(SELECT (1), (2) AS col1, (3), (4) AS col2) GROUP BY col1", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.IntegerType() # type: ignore + assert query.select.projection[1].type == ct.IntegerType() # type: ignore + + +def test_grouping_id_func(session: Session): + """ + Test the `grouping_id` function + """ + query = parse( + "SELECT grouping_id(col1, col2) FROM " + "(SELECT (1), (2) AS col1, (3), (4) AS col2) GROUP BY col1", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.BigIntType() # type: ignore + + +def test_hash_func(session: Session): + """ + Test the `hash` function + """ + query = parse("SELECT hash('hello'), hash(123)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.IntegerType() # type: ignore + assert query.select.projection[1].type == ct.IntegerType() # type: ignore + + +def test_hex_func(session: Session): + """ + Test the `hex` function + """ + query = parse("SELECT hex('hello'), hex(123)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.StringType() # type: ignore + assert query.select.projection[1].type == ct.StringType() # type: ignore + + +def test_histogram_numeric_func(session: Session): + """ + Test the `histogram_numeric` function + """ + query = parse("SELECT histogram_numeric(col, 5) FROM (SELECT (1), (2) AS col)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert isinstance(query.select.projection[0].type, ct.ListType) # type: ignore + assert isinstance(query.select.projection[0].type.element.type, ct.StructType) # type: ignore + + +def test_hour_func(session: Session): + """ + Test the `hour` function + """ + query = parse( + "SELECT hour(cast('2023-01-01 12:34:56' as timestamp)), hour('2023-01-01 23:45:56')", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.IntegerType() # type: ignore + assert query.select.projection[1].type == ct.IntegerType() # type: ignore + + +def test_hypot_func(session: Session): + """ + Test the `hypot` function + """ + query = parse("SELECT hypot(3, 4), hypot(5.0, 12.0)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.FloatType() # type: ignore + assert query.select.projection[1].type == ct.FloatType() # type: ignore + + +def test_ilike_like_func(session: Session): + """ + Test the `ilike`, `like` functions + """ + query = parse( + "SELECT col1 ilike '%pattern%', ilike(col1, '%pattern%'), " + "like(col1, '%pattern%') FROM (SELECT ('aee'), ('bee') AS col1)", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.BooleanType() # type: ignore + assert query.select.projection[1].type == ct.BooleanType() # type: ignore + assert query.select.projection[2].type == ct.BooleanType() # type: ignore + + +def test_initcap_func(session: Session): + """ + Test the `initcap` function + """ + query = parse("SELECT initcap('hello world'), initcap('SQL function')") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.StringType() # type: ignore + assert query.select.projection[1].type == ct.StringType() # type: ignore + + +def test_inline_func(session: Session): + """ + Test the `inline` function + """ + # This test assumes there's a table with a column of type ARRAY> + query = parse( + "SELECT inline(col1), inline_outer(array(struct(1, 'a'), struct(2, 'b')))" + " FROM (SELECT (array(struct('a', 1, 'b', '222'))) AS col1)", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert isinstance(query.select.projection[0].type, ct.StructType) # type: ignore + assert isinstance(query.select.projection[1].type, ct.StructType) # type: ignore + + +def test_input_file_block_length_func(session: Session): + """ + Test the `input_file_block_length` function + """ + query = parse("SELECT input_file_block_length()") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.LongType() # type: ignore + + +def test_input_file_block_start_func(session: Session): + """ + Test the `input_file_block_start` function + """ + query = parse("SELECT input_file_block_start()") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.LongType() # type: ignore + + +def test_input_file_name_func(session: Session): + """ + Test the `input_file_name` function + """ + query = parse("SELECT input_file_name()") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.StringType() # type: ignore + + +def test_int(session: Session): + """ + Test the `int` function + """ + query = parse("SELECT int('3')") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.IntegerType() # type: ignore + + +def test_instr_func(session: Session): + """ + Test the `instr` function + """ + query = parse("SELECT instr('hello world', 'world'), instr('hello world', 'SQL')") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.IntegerType() # type: ignore + assert query.select.projection[1].type == ct.IntegerType() # type: ignore + + +def test_isnan_func(session: Session): + """ + Test the `isnan` function + """ + query = parse("SELECT isnan(1/0), isnan(0.0/0.0)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.BooleanType() # type: ignore + assert query.select.projection[1].type == ct.BooleanType() # type: ignore + + +def test_isnotnull_isnull(session: Session): + """ + Test the `isnotnull`, `isnull` functions + """ + query = parse("SELECT isnotnull(0), isnull(null)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.BooleanType() # type: ignore + assert query.select.projection[1].type == ct.BooleanType() # type: ignore + + +def test_json_array_length_func(session: Session): + """ + Test the `json_array_length` function + """ + query = parse("SELECT json_array_length('[1, 2, 3]')") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.IntegerType() # type: ignore + + +def test_json_object_keys_func(session: Session): + """ + Test the `json_object_keys` function + """ + query = parse('SELECT json_object_keys(\'{"key1": "value1", "key2": "value2"}\')') + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert isinstance(query.select.projection[0].type, ct.ListType) # type: ignore + assert query.select.projection[0].type.element.type == ct.StringType() # type: ignore + + +def test_json_tuple_func(session: Session): + """ + Test the `json_tuple` function + """ + query = parse( + 'SELECT json_tuple(\'{"key1": "value1", "key2": "value2"}\', \'key1\', \'key2\')', + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert isinstance(query.select.projection[0].type, ct.ListType) # type: ignore + + +def test_kurtosis_func(session: Session): + """ + Test the `kurtosis` function + """ + query = parse("SELECT kurtosis(col) FROM (SELECT (1), (2), (3) AS col)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.DoubleType() # type: ignore + + +def test_lag_func(session: Session): + """ + Test the `lag` function + """ + query = parse( + "SELECT lag(col) OVER (ORDER BY col) FROM (SELECT (1), (2), (3) AS col)", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + # The output type depends on the type of `col` + assert query.select.projection[0].type == ct.IntegerType() # type: ignore + + +def test_last_and_last_value(session: Session): + """ + Test the `last` function + """ + query = parse( + "SELECT last(col) OVER (PARTITION BY col2 ORDER BY col3), " + "last_value(col) OVER (PARTITION BY col2 ORDER BY col3) " + "FROM (SELECT (1), (2), (3) AS col, (1), (2), (3) AS col2, " + "(3), (4), (5) as col3)", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + # The output type depends on the type of `col` + assert query.select.projection[0].type == ct.IntegerType() # type: ignore + assert query.select.projection[1].type == ct.IntegerType() # type: ignore + + +def test_last_day_func(session: Session): + """ + Test the `last_day` function + """ + query = parse("SELECT last_day('2023-01-01'), last_day(cast('2023-02-15' as date))") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.DateType() # type: ignore + assert query.select.projection[1].type == ct.DateType() # type: ignore + + +def test_lcase_func(session: Session): + """ + Test the `lcase` function + """ + query = parse("SELECT lcase('HELLO'), lcase('WORLD')") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.StringType() # type: ignore + assert query.select.projection[1].type == ct.StringType() # type: ignore + + +def test_lead_func(session: Session): + """ + Test the `lead` function + """ + query = parse( + "SELECT lead(col, 1, 'N/A') OVER (ORDER BY col) FROM (SELECT ('1'), ('a'), ('x') AS col)", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + # The output type depends on the type of `col` + # Assuming `col` is of type StringType for this test + assert query.select.projection[0].type == ct.StringType() # type: ignore + + +def test_least_func(session: Session): + """ + Test the `least` function + """ + query = parse("SELECT least(10, 20, 30, 40)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.IntegerType() # type: ignore + + +# TODO: figure out why antlr parser fails +# def test_left_func(session: Session): +# """ +# Test the `left` function +# """ +# query = parse("SELECT left('hello world', 5)") +# exc = DJException() +# ctx = ast.CompileContext(session=session, exception=exc) +# query.compile(ctx) +# assert not exc.errors +# assert query.select.projection[0].type == ct.StringType() # type: ignore +# assert query.select.projection[1].type == ct.StringType() # type: ignore + + +def test_len_func(session: Session): + """ + Test the `len` function + """ + query = parse("SELECT len('hello'), len('world')") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.IntegerType() # type: ignore + assert query.select.projection[1].type == ct.IntegerType() # type: ignore + + +def test_like_func(session: Session): + """ + Test the `like` function + """ + query = parse( + "SELECT col like '%pattern%' FROM (SELECT ('a'), ('b'), ('c') AS col)", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.BooleanType() # type: ignore + + +def test_localtimestamp_func(session: Session): + """ + Test the `localtimestamp` function + """ + query = parse("SELECT localtimestamp()") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.TimestampType() # type: ignore + + +def test_locate_func(session: Session): + """ + Test the `locate` function + """ + query = parse( + "SELECT locate('world', 'hello world'), locate('SQL', 'hello world', 2)", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.IntegerType() # type: ignore + assert query.select.projection[1].type == ct.IntegerType() # type: ignore + + +def test_log_functions(session: Session): + """ + Test the `log1p`, `log10` and `log2` functions + """ + query = parse( + "SELECT log1p(col), log10(col), log2(col) FROM (SELECT (1), (2), (3) as col)", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.DoubleType() # type: ignore + assert query.select.projection[1].type == ct.DoubleType() # type: ignore + assert query.select.projection[2].type == ct.DoubleType() # type: ignore + + +def test_lpad_func(session: Session): + """ + Test the `lpad` function + """ + query = parse("SELECT lpad('hello', 10, ' '), lpad('SQL', 5, '0')") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.StringType() # type: ignore + assert query.select.projection[1].type == ct.StringType() # type: ignore + + +def test_ltrim_func(session: Session): + """ + Test the `ltrim` function + """ + query = parse("SELECT ltrim(' hello'), ltrim('-----world-', '-')") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.StringType() # type: ignore + assert query.select.projection[1].type == ct.StringType() # type: ignore + + +def test_make_date_func(session: Session): + """ + Test the `make_date` function + """ + query = parse("SELECT make_date(2023, 7, 30), make_date(2023, 12, 31)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.DateType() # type: ignore + assert query.select.projection[1].type == ct.DateType() # type: ignore + + +def test_make_dt_interval_func(session: Session): + """ + Test the `make_dt_interval` function + """ + query = parse("SELECT make_dt_interval(1, 2, 30, 45)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.DayTimeIntervalType() # type: ignore + + +def test_make_interval_func(session: Session): + """ + Test the `make_interval` function + """ + query = parse("SELECT make_interval(1, 6)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.YearMonthIntervalType() # type: ignore + + +def test_make_timestamp_func(session: Session): + """ + Test the `make_timestamp` function + """ + query = parse("SELECT make_timestamp(2023, 7, 30, 14, 45, 30)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.TimestampType() # type: ignore + + +def test_make_timestamp_ltz_func(session: Session): + """ + Test the `make_timestamp_ltz` function + """ + query = parse( + "SELECT make_timestamp_ltz(2023, 7, 30, 14, 45, 30, 'UTC'), " + "make_timestamp_ltz(2023, 7, 30, 14, 45, 30)", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.TimestampType() # type: ignore + assert query.select.projection[1].type == ct.TimestampType() # type: ignore + + +def test_make_timestamp_ntz_func(session: Session): + """ + Test the `make_timestamp_ntz` function + """ + query = parse("SELECT make_timestamp_ntz(2023, 7, 30, 14, 45, 30)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.TimestampType() # type: ignore + + +def test_make_ym_interval_func(session: Session): + """ + Test the `make_ym_interval` function + """ + query = parse("SELECT make_ym_interval(1, 6)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.YearMonthIntervalType() # type: ignore + + +def test_map_concat_func(session: Session): + """ + Test the `map_concat` function + """ + query = parse("SELECT map_concat(map(1, 'a', 2, 'b'), map(1, 'c'))") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.MapType(key_type=ct.IntegerType(), value_type=ct.StringType()) # type: ignore + + +def test_map_contains_key_func(session: Session): + """ + Test the `map_contains_key` function + """ + query = parse("SELECT map_contains_key(map(1, 'a', 2, 'b'), 1)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.BooleanType() # type: ignore + + +def test_map_entries_func(session: Session): + """ + Test the `map_entries` function + """ + query = parse("SELECT map_entries(map(1, 'a', 2, 'b'))") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.ListType( # type: ignore + element_type=ct.StructType( + ct.NestedField(name="key", field_type=ct.IntegerType()), # type: ignore + ct.NestedField(name="value", field_type=ct.StringType()), # type: ignore + ), + ) # type: ignore + + +def test_map_filter_func(session: Session): + """ + Test the `map_filter` function + """ + query = parse("SELECT map_filter(map(1, 0, 2, 2, 3, -1), (k, v) -> k > v)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert query.select.projection[0].type == ct.MapType( # type: ignore + key_type=ct.IntegerType(), + value_type=ct.IntegerType(), + ) + + +def test_map_from_arrays_func(session: Session): + """ + Test the `map_from_arrays` function + """ + query = parse("SELECT map_from_arrays(array(1.0, 3.0), array('2', '4'))") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.MapType( # type: ignore + key_type=ct.FloatType(), + value_type=ct.StringType(), + ) + + +def test_map_from_entries_func(session: Session): + """ + Test the `map_from_entries` function + """ + query = parse("SELECT map_from_entries(array(struct(1, 'a'), struct(2, 'b')))") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.MapType( # type: ignore + key_type=ct.IntegerType(), + value_type=ct.StringType(), + ) + + +def test_map_keys_func(session: Session): + """ + Test the `map_keys` function + """ + query = parse("SELECT map_keys(map(1, 'a', 2, 'b'))") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.ListType(element_type=ct.IntegerType()) # type: ignore + + +def test_map_values_func(session: Session): + """ + Test the `map_values` function + """ + query = parse("SELECT map_values(map(1, 'a', 2, 'b'))") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.ListType(element_type=ct.StringType()) # type: ignore + + +def test_max() -> None: + """ + Test ``Max`` function. + """ + assert ( + Max.infer_type(ast.Column(ast.Name("x"), _type=IntegerType())) == IntegerType() + ) + assert Max.infer_type(ast.Column(ast.Name("x"), _type=BigIntType())) == BigIntType() + assert Max.infer_type(ast.Column(ast.Name("x"), _type=FloatType())) == FloatType() + assert Max.infer_type( + ast.Column(ast.Name("x"), _type=DecimalType(8, 6)), + ) == DecimalType(8, 6) + assert ( + Max.infer_type( + ast.Column(ast.Name("x"), _type=StringType()), + ) + == StringType() + ) + + +# TODO +# def test_map_zip_with_func(session: Session): +# """ +# Test the `map_zip_with` function +# """ +# # The third argument to map_zip_with is a function, which needs special handling +# # Assuming that we have a function "func" defined elsewhere in the code +# query = parse("SELECT map_zip_with(map_col1, map_col2, func) FROM table") +# exc = DJException() +# ctx = ast.CompileContext(session=session, exception=exc) +# query.compile(ctx) +# assert not exc.errors +# assert isinstance(query.select.projection[0].type, ct.MapType) # type: ignore + + +def test_mask_func(session: Session): + """ + Test the `mask` function + """ + query = parse( + "SELECT mask('abcd-EFGH-8765-4321'), " + "mask('abcd-EFGH-8765-4321', 'Q'), " + "mask('AbCD123-@$#', 'Q', 'q'), " + "mask('AbCD123-@$#', 'Q', 'q', 'd'), " + "mask('AbCD123-@$#', 'Q', 'q', 'd', 'o')", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.StringType() # type: ignore + assert query.select.projection[1].type == ct.StringType() # type: ignore + assert query.select.projection[2].type == ct.StringType() # type: ignore + assert query.select.projection[3].type == ct.StringType() # type: ignore + assert query.select.projection[4].type == ct.StringType() # type: ignore + + +def test_max_by_min_by_funcs(session: Session): + """ + Test the `max_by` function + """ + query = parse( + "SELECT max_by(x, y), min_by(x, y) " + "FROM (SELECT ('a'), ('b'), ('c') AS x, (10), (50), (20) AS y)", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.StringType() # type: ignore + assert query.select.projection[1].type == ct.StringType() # type: ignore + + +def test_md5_func(session: Session): + """ + Test the `md5` function + """ + query = parse("SELECT md5('Spark')") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.StringType() # type: ignore + + +def test_mean_func(session: Session): + """ + Test the `mean` function + """ + query = parse("SELECT mean(col) FROM (SELECT (1), (2), (3) AS col)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.DoubleType() # type: ignore + + +def test_median_func(session: Session): + """ + Test the `median` function + """ + query = parse("SELECT median(col) FROM (SELECT (1), (2), (3) AS col)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.DoubleType() # type: ignore + + +def test_min() -> None: + """ + Test ``Min`` function. + """ + assert ( + Min.infer_type(ast.Column(ast.Name("x"), _type=IntegerType())) == IntegerType() + ) + assert Min.infer_type(ast.Column(ast.Name("x"), _type=BigIntType())) == BigIntType() + assert Min.infer_type(ast.Column(ast.Name("x"), _type=FloatType())) == FloatType() + assert Min.infer_type( + ast.Column(ast.Name("x"), _type=DecimalType(8, 6)), + ) == DecimalType(8, 6) + with pytest.raises(Exception): + Min.infer_type( # pylint: disable=expression-not-assigned + ast.Column(ast.Name("x"), _type=StringType()), + ) == StringType() + + +def test_minute(session: Session): + """ + Test the `minute` function + """ + query = parse( + "SELECT minute('2009-07-30 12:58:59'), minute(cast('2009-07-30 12:58:59' as timestamp))", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.IntegerType() # type: ignore + assert query.select.projection[1].type == ct.IntegerType() # type: ignore + + +def test_mod(session: Session): + """ + Test the `mod` function + """ + query = parse( + "SELECT MOD(2, 1.8)", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.FloatType() # type: ignore + + +def test_mode(session: Session): + """ + Test the `mode` function + """ + query = parse( + "SELECT mode(col) FROM (SELECT (0), (10), (10) AS col)", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.IntegerType() # type: ignore + + +def test_monotonically_increasing_id(session: Session): + """ + Test the `monotonically_increasing_id` function + """ + query = parse( + "SELECT monotonically_increasing_id()", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.BigIntType() # type: ignore + + +def test_months_between(session: Session): + """ + Test the `months_between` function + """ + query = parse( + "SELECT months_between('1997-02-28 10:30:00', '1996-10-30'), " + "months_between(cast('1997-02-28 10:30:00' as timestamp), cast('1996-10-30' as timestamp)), " + "months_between('1997-02-28 10:30:00', '1996-10-30', false)", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.FloatType() # type: ignore + + +def test_named_struct_func(session: Session): + """ + Test the `named_struct` function + """ + query = parse("SELECT named_struct('name', 'cactus', 'age', 30)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.StructType( # type: ignore + ct.NestedField(name="name", field_type=ct.StringType()), # type: ignore + ct.NestedField(name="age", field_type=ct.IntegerType()), # type: ignore + ) + + +def test_nanvl_func(session: Session): + """ + Test the `nanvl` function + """ + query = parse("SELECT nanvl(cast('NaN' as double), 123)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.DoubleType() # type: ignore + + +def test_negative_func(session: Session): + """ + Test the `negative` function + """ + query = parse("SELECT negative(1)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.IntegerType() # type: ignore + + +def test_next_day_func(session: Session): + """ + Test the `next_day` function + """ + query = parse("SELECT next_day('2015-01-14', 'TU')") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.DateType() # type: ignore + + +def test_not_func(session: Session): + """ + Test the `not` function + """ + query = parse("SELECT not(true)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.BooleanType() # type: ignore + + +def test_now() -> None: + """ + Test ``Now`` function. + """ + assert Now.infer_type() == ct.TimestampType() + + +def test_nth_value_func(session: Session): + """ + Test the `nth_value` function + """ + query = parse( + "SELECT a, b, nth_value(b, 2) OVER (PARTITION BY a ORDER BY b) FROM " + "(SELECT ('A1'), ('A2'), ('A1') AS a, (2), (1), (3) AS b)", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[2].type == ct.IntegerType() # type: ignore + + +def test_ntile_func(session: Session): + """ + Test the `ntile` function + """ + query = parse( + "SELECT a, b, ntile(2) OVER (PARTITION BY a ORDER BY b) FROM" + "(SELECT ('A1'), ('A2'), ('A1') AS a, (2), (1), (3) AS b)", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[2].type == ct.IntegerType() # type: ignore + + +def test_nullif_func(session: Session): + """ + Test the `nullif` function + """ + query = parse("SELECT nullif(2, 2)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.IntegerType() # type: ignore + + +def test_nvl_func(session: Session): + """ + Test the `nvl` function + """ + query = parse("SELECT nvl(array('1'), array('2'))") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.ListType(element_type=ct.StringType()) # type: ignore + + +def test_nvl2_func(session: Session): + """ + Test the `nvl2` function + """ + query = parse("SELECT nvl2(3, NULL, 1)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.IntegerType() # type: ignore + + +def test_octet_length_func(session: Session): + """ + Test the `octet_length` function + """ + query = parse("SELECT octet_length('Spark SQL')") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.IntegerType() # type: ignore + + +def test_overlay_func(session: Session): + """ + Test the `overlay` function + TODO: support syntax like: # pylint: disable=fixme + SELECT overlay(encode('Spark SQL', 'utf-8') PLACING encode('_', 'utf-8') FROM 6); + """ + query = parse("SELECT overlay('Hello World', 'J', 7)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.StringType() # type: ignore + + +def test_rank(session: Session): + """ + Test `rank` + """ + query = parse( + "SELECT rank() OVER (PARTITION BY col ORDER BY col) FROM (SELECT (1), (2) AS col)", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.IntegerType() # type: ignore + + +def test_regexp_like(session: Session): + """ + Test `regexp_like` + """ + query = parse( + "SELECT regexp_like('%SystemDrive%\\Users\\John', '%SystemDrive%\\Users.*')", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.BooleanType() # type: ignore + + +def test_row_number(session: Session): + """ + Test `row_number` + """ + query = parse( + "SELECT row_number() OVER (PARTITION BY col ORDER BY col) FROM (SELECT (1), (2) AS col)", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.IntegerType() # type: ignore + + +def test_round(session: Session): + """ + Test `round` + """ + query = parse( + "SELECT round(2.5, 0)", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.IntegerType() # type: ignore + + query = parse( + "SELECT round(2.5)", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.IntegerType() # type: ignore + + +def test_size(session: Session): + """ + Test the `size` Spark function + """ + query = parse("SELECT size(array('b', 'd', 'c', 'a'))") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.IntegerType() # type: ignore + + query = parse("SELECT size(map('a', 1, 'b', 2))") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.IntegerType() # type: ignore + + +def test_split(session: Session): + """ + Test the `split` Spark function + """ + query = parse("SELECT split('oneAtwoBthreeC', '[ABC]')") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.ListType(element_type=ct.StringType()) # type: ignore + + +def test_strpos(session: Session): + """ + Test `strpos` + """ + query = parse("SELECT strpos('abcde', 'cde'), strpos('abcde', 'cde', 4)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.IntegerType() # type: ignore + assert query.select.projection[1].type == ct.IntegerType() # type: ignore + + +def test_substring(session: Session): + """ + Test `substring` + """ + query = parse("SELECT substring('Spark SQL', 5), substring('Spark SQL', 5, 1)") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.StringType() # type: ignore + assert query.select.projection[1].type == ct.StringType() # type: ignore + + +def test_sum() -> None: + """ + Test ``sum`` function. + """ + assert ( + Sum.infer_type(ast.Column(ast.Name("x"), _type=IntegerType())) == BigIntType() + ) + assert Sum.infer_type(ast.Column(ast.Name("x"), _type=FloatType())) == DoubleType() + assert Sum.infer_type( + ast.Column(ast.Name("x"), _type=DecimalType(8, 6)), + ) == DecimalType(18, 6) + + +def test_transform(session: Session): + """ + Test the `transform` Spark function + """ + query = parse( + """ + SELECT transform(array(1, 2, 3), x -> x + 1) + """, + ) + ctx = ast.CompileContext(session=session, exception=DJException()) + query.compile(ctx) + assert query.select.projection[0].type == ct.ListType(element_type=ct.IntegerType()) # type: ignore + + query = parse( + """ + SELECT transform(array(1, 2, 3), (x, i) -> x + i) + """, + ) + ctx = ast.CompileContext(session=session, exception=DJException()) + query.compile(ctx) + assert query.select.projection[0].type == ct.ListType(element_type=ct.IntegerType()) # type: ignore + + +def test_to_date() -> None: + """ + Test ``to_date`` function. + """ + assert ( + ToDate.infer_type(ast.Column(ast.Name("x"), _type=StringType())) == DateType() + ) + + +def test_trim(session: Session): + """ + Test `trim` + """ + query = parse("SELECT trim(' lmgi ')") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.StringType() # type: ignore + + +def test_unhex_func(session: Session): + """ + Test the `unhex` function + """ + query = parse("SELECT unhex('4D'), unhex('7953514C')") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.BinaryType() # type: ignore + assert query.select.projection[1].type == ct.BinaryType() # type: ignore + + +def test_upper(session: Session): + """ + Test `upper` + """ + query = parse("SELECT upper('abcde')") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + assert query.select.projection[0].type == ct.StringType() # type: ignore diff --git a/datajunction-server/tests/sql/parsing/__init__.py b/datajunction-server/tests/sql/parsing/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/datajunction-server/tests/sql/parsing/backends/__init__.py b/datajunction-server/tests/sql/parsing/backends/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/datajunction-server/tests/sql/parsing/backends/antlr4_test.py b/datajunction-server/tests/sql/parsing/backends/antlr4_test.py new file mode 100644 index 000000000..a807447e9 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/backends/antlr4_test.py @@ -0,0 +1,121 @@ +""" +Tests for custom antlr4 parser +""" +# mypy: ignore-errors + +import pytest + +from datajunction_server.sql.parsing.backends.antlr4 import parse + + +@pytest.mark.parametrize( + "query_string", + [ + """SELECT suit, key, value + FROM suites_and_ranks_arrays + LATERAL VIEW EXPLODE(rankmap) AS key, value + ORDER BY suit;""", + """SELECT suit, exploded_rank + FROM suites_and_ranks_arrays + LATERAL VIEW EXPLODE(rank) exploded_rank + ORDER BY suit;""", + """SELECT suit, exploded_rank, key, value + FROM suites_and_ranks_arrays + LATERAL VIEW EXPLODE(rank) AS exploded_rank + LATERAL VIEW EXPLODE(rankmap) AS key, value + ORDER BY suit;""", + ], +) +def test_antlr4_backend_lateral_view_explode(query_string): + """ + Test LATERAL VIEW EXPLODE queries + """ + parse(query_string) + + +@pytest.mark.parametrize( + "query_string", + [ + """Select suit, exploded_rank, exploded_rank2 + from suites_and_ranks_arrays + CROSS JOIN UNNEST(rank) as t(exploded_rank) + ORDER BY suit;""", + """Select suit, exploded_rank, exploded_rank2 + from suites_and_ranks_arrays + CROSS JOIN UNNEST(rank, rank) as t(exploded_rank, exploded_rank2) + ORDER BY suit;""", + """Select suit, exploded_rank, exploded_rank2 + from suites_and_ranks_arrays + CROSS JOIN UNNEST(rank) as t(exploded_rank) + CROSS JOIN UNNEST(rank) as t(exploded_rank2)""", + """Select suit, key, value + from suites_and_ranks_arrays + CROSS JOIN UNNEST(rankmap) as t(key, value) + ORDER BY suit;""", + """Select suit, exploded_rank, k, value + from suites_and_ranks_arrays + CROSS JOIN UNNEST(rank, rankmap) as t(exploded_rank, key, value) + ORDER BY suit;""", + ], +) +def test_antlr4_backend_cross_join_unnest(query_string): + """ + Test CROSS JOIN UNNEST queries + """ + parse(query_string) + + +def test_antlr4_backend_predicate_like(): + """ + Test LIKE predicate + """ + query = parse("SELECT * FROM person WHERE name LIKE '%$_%';") + assert "LIKE '%$_%'" in str(query) + + +def test_antlr4_backend_predicate_ilike(): + """ + Test ILIKE predicate + """ + query = parse("SELECT * FROM person WHERE name ILIKE '%foo%';") + assert "ILIKE '%foo%'" in str(query) + + +def test_antlr4_backend_predicate_rlike(): + """ + Test RLIKE predicate + """ + query = parse("SELECT * FROM person WHERE name RLIKE 'M+';") + assert "RLIKE 'M+'" in str(query) + + +def test_antlr4_backend_predicate_is_distinct_from(): + """ + Test IS DISTINCT FROM predicate + """ + query = parse("SELECT * FROM person WHERE name IS DISTINCT FROM 'Bob'") + assert "IS DISTINCT FROM 'Bob'" in str(query) + + +def test_antlr4_backend_trim(): + """ + Test trim + """ + query = parse("SELECT TRIM(BOTH FROM ' SparkSQL ');") + assert "TRIM( BOTH FROM ' SparkSQL ')" in str(query) + query = parse("SELECT TRIM(LEADING FROM ' SparkSQL ');") + assert "TRIM( LEADING FROM ' SparkSQL ')" in str(query) + query = parse("SELECT TRIM(TRAILING FROM ' SparkSQL ');") + assert "TRIM( TRAILING FROM ' SparkSQL ')" in str(query) + query = parse("SELECT TRIM(' SparkSQL ');") + assert "TRIM(' SparkSQL ')" in str(query) + + +def test_antlr4_lambda_function(): + """ + Test a lambda function using `->` + """ + query = parse("SELECT FOO('a', 'b', c -> d) AS e;") + assert "FOO('a', 'b', c -> d) AS e" in str(query) + query = parse("SELECT FOO('a', 'b', (c, c2, c3) -> d) AS e;") + assert "FOO('a', 'b', (c, c2, c3) -> d) AS e" in str(query) diff --git a/datajunction-server/tests/sql/parsing/backends/types_test.py b/datajunction-server/tests/sql/parsing/backends/types_test.py new file mode 100644 index 000000000..91e9603ca --- /dev/null +++ b/datajunction-server/tests/sql/parsing/backends/types_test.py @@ -0,0 +1,37 @@ +""" +Tests for types +""" +import datajunction_server.sql.parsing.types as ct +from datajunction_server.sql.parsing import ast + + +def test_types_compatible(): + """ + Checks whether type compatibility checks work + """ + assert ct.IntegerType().is_compatible(ct.IntegerType()) + assert ct.IntegerType().is_compatible(ct.BigIntType()) + assert ct.BigIntType().is_compatible(ct.IntegerType()) + assert ct.TinyIntType().is_compatible(ct.BigIntType()) + assert ct.BigIntType().is_compatible(ct.BigIntType()) + assert ct.BigIntType().is_compatible(ct.IntegerType()) + assert ct.FloatType().is_compatible(ct.DoubleType()) + assert ct.StringType().is_compatible(ct.VarcharType()) + assert ct.DateType().is_compatible(ct.TimeType()) + + assert not ct.StringType().is_compatible(ct.IntegerType()) + assert not ct.StringType().is_compatible(ct.BooleanType()) + assert not ct.StringType().is_compatible(ct.BinaryType()) + assert not ct.StringType().is_compatible(ct.BigIntType()) + assert not ct.StringType().is_compatible(ct.DateType()) + + +def test_varchar_in_ast(): + """ + Test that varchar types support length as a parameter. + """ + cast_expr = ast.Cast( + data_type=ast.ColumnType("varchar(10)"), + expression=ast.Column(ast.Name("abc")), + ) + assert str(cast_expr) == "CAST(abc AS VARCHAR(10))" diff --git a/datajunction-server/tests/sql/parsing/queries/__init__.py b/datajunction-server/tests/sql/parsing/queries/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query1.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query1.sql new file mode 100644 index 000000000..8808e3ce6 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query1.sql @@ -0,0 +1,23 @@ +-- start query 1 in stream 0 using template query1.tpl +WITH customer_total_return + AS (SELECT sr_customer_sk AS ctr_customer_sk, + sr_store_sk AS ctr_store_sk, + Sum(sr_return_amt) AS ctr_total_return + FROM store_returns, + date_dim + WHERE sr_returned_date_sk = d_date_sk + AND d_year = 2001 + GROUP BY sr_customer_sk, + sr_store_sk) +SELECT c_customer_id +FROM customer_total_return ctr1, + store, + customer +WHERE ctr1.ctr_total_return > (SELECT Avg(ctr_total_return) * 1.2 + FROM customer_total_return ctr2 + WHERE ctr1.ctr_store_sk = ctr2.ctr_store_sk) + AND s_store_sk = ctr1.ctr_store_sk + AND s_state = 'TN' + AND ctr1.ctr_customer_sk = c_customer_sk +ORDER BY c_customer_id +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query10.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query10.sql new file mode 100644 index 000000000..0cb0ba050 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query10.sql @@ -0,0 +1,62 @@ +-- start query 10 in stream 0 using template query10.tpl +SELECT cd_gender, + cd_marital_status, + cd_education_status, + Count(*) cnt1, + cd_purchase_estimate, + Count(*) cnt2, + cd_credit_rating, + Count(*) cnt3, + cd_dep_count, + Count(*) cnt4, + cd_dep_employed_count, + Count(*) cnt5, + cd_dep_college_count, + Count(*) cnt6 +FROM customer c, + customer_address ca, + customer_demographics +WHERE c.c_current_addr_sk = ca.ca_address_sk + AND ca_county IN ( 'Lycoming County', 'Sheridan County', + 'Kandiyohi County', + 'Pike County', + 'Greene County' ) + AND cd_demo_sk = c.c_current_cdemo_sk + AND EXISTS (SELECT * + FROM store_sales, + date_dim + WHERE c.c_customer_sk = ss_customer_sk + AND ss_sold_date_sk = d_date_sk + AND d_year = 2002 + AND d_moy BETWEEN 4 AND 4 + 3) + AND ( EXISTS (SELECT * + FROM web_sales, + date_dim + WHERE c.c_customer_sk = ws_bill_customer_sk + AND ws_sold_date_sk = d_date_sk + AND d_year = 2002 + AND d_moy BETWEEN 4 AND 4 + 3) + OR EXISTS (SELECT * + FROM catalog_sales, + date_dim + WHERE c.c_customer_sk = cs_ship_customer_sk + AND cs_sold_date_sk = d_date_sk + AND d_year = 2002 + AND d_moy BETWEEN 4 AND 4 + 3) ) +GROUP BY cd_gender, + cd_marital_status, + cd_education_status, + cd_purchase_estimate, + cd_credit_rating, + cd_dep_count, + cd_dep_employed_count, + cd_dep_college_count +ORDER BY cd_gender, + cd_marital_status, + cd_education_status, + cd_purchase_estimate, + cd_credit_rating, + cd_dep_count, + cd_dep_employed_count, + cd_dep_college_count +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query11.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query11.sql new file mode 100644 index 000000000..cf24b9b74 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query11.sql @@ -0,0 +1,97 @@ +-- start query 11 in stream 0 using template query11.tpl +WITH year_total + AS (SELECT c_customer_id customer_id, + c_first_name customer_first_name + , + c_last_name + customer_last_name, + c_preferred_cust_flag + customer_preferred_cust_flag + , + c_birth_country + customer_birth_country, + c_login customer_login, + c_email_address + customer_email_address, + d_year dyear, + Sum(ss_ext_list_price - ss_ext_discount_amt) year_total, + 's' sale_type + FROM customer, + store_sales, + date_dim + WHERE c_customer_sk = ss_customer_sk + AND ss_sold_date_sk = d_date_sk + GROUP BY c_customer_id, + c_first_name, + c_last_name, + c_preferred_cust_flag, + c_birth_country, + c_login, + c_email_address, + d_year + UNION ALL + SELECT c_customer_id customer_id, + c_first_name customer_first_name + , + c_last_name + customer_last_name, + c_preferred_cust_flag + customer_preferred_cust_flag + , + c_birth_country + customer_birth_country, + c_login customer_login, + c_email_address + customer_email_address, + d_year dyear, + Sum(ws_ext_list_price - ws_ext_discount_amt) year_total, + 'w' sale_type + FROM customer, + web_sales, + date_dim + WHERE c_customer_sk = ws_bill_customer_sk + AND ws_sold_date_sk = d_date_sk + GROUP BY c_customer_id, + c_first_name, + c_last_name, + c_preferred_cust_flag, + c_birth_country, + c_login, + c_email_address, + d_year) +SELECT t_s_secyear.customer_id, + t_s_secyear.customer_first_name, + t_s_secyear.customer_last_name, + t_s_secyear.customer_birth_country +FROM year_total t_s_firstyear, + year_total t_s_secyear, + year_total t_w_firstyear, + year_total t_w_secyear +WHERE t_s_secyear.customer_id = t_s_firstyear.customer_id + AND t_s_firstyear.customer_id = t_w_secyear.customer_id + AND t_s_firstyear.customer_id = t_w_firstyear.customer_id + AND t_s_firstyear.sale_type = 's' + AND t_w_firstyear.sale_type = 'w' + AND t_s_secyear.sale_type = 's' + AND t_w_secyear.sale_type = 'w' + AND t_s_firstyear.dyear = 2001 + AND t_s_secyear.dyear = 2001 + 1 + AND t_w_firstyear.dyear = 2001 + AND t_w_secyear.dyear = 2001 + 1 + AND t_s_firstyear.year_total > 0 + AND t_w_firstyear.year_total > 0 + AND CASE + WHEN t_w_firstyear.year_total > 0 THEN t_w_secyear.year_total / + t_w_firstyear.year_total + ELSE 0.0 + END > CASE + WHEN t_s_firstyear.year_total > 0 THEN + t_s_secyear.year_total / + t_s_firstyear.year_total + ELSE 0.0 + END +ORDER BY t_s_secyear.customer_id, + t_s_secyear.customer_first_name, + t_s_secyear.customer_last_name, + t_s_secyear.customer_birth_country +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query12.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query12.sql new file mode 100644 index 000000000..5f6d721c4 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query12.sql @@ -0,0 +1,30 @@ +-- start query 12 in stream 0 using template query12.tpl +SELECT + i_item_id , + i_item_desc , + i_category , + i_class , + i_current_price , + Sum(ws_ext_sales_price) AS itemrevenue , + Sum(ws_ext_sales_price)*100/Sum(Sum(ws_ext_sales_price)) OVER (partition BY i_class) AS revenueratio +FROM web_sales , + item , + date_dim +WHERE ws_item_sk = i_item_sk +AND i_category IN ('Home', + 'Men', + 'Women') +AND ws_sold_date_sk = d_date_sk +AND d_date BETWEEN Cast('2000-05-11' AS DATE) AND ( + Cast('2000-05-11' AS DATE) + INTERVAL '30' day) +GROUP BY i_item_id , + i_item_desc , + i_category , + i_class , + i_current_price +ORDER BY i_category , + i_class , + i_item_id , + i_item_desc , + revenueratio +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query13.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query13.sql new file mode 100644 index 000000000..2bec54b95 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query13.sql @@ -0,0 +1,44 @@ +-- start query 13 in stream 0 using template query13.tpl +SELECT Avg(ss_quantity), + Avg(ss_ext_sales_price), + Avg(ss_ext_wholesale_cost), + Sum(ss_ext_wholesale_cost) +FROM store_sales, + store, + customer_demographics, + household_demographics, + customer_address, + date_dim +WHERE s_store_sk = ss_store_sk + AND ss_sold_date_sk = d_date_sk + AND d_year = 2001 + AND ( ( ss_hdemo_sk = hd_demo_sk + AND cd_demo_sk = ss_cdemo_sk + AND cd_marital_status = 'U' + AND cd_education_status = 'Advanced Degree' + AND ss_sales_price BETWEEN 100.00 AND 150.00 + AND hd_dep_count = 3 ) + OR ( ss_hdemo_sk = hd_demo_sk + AND cd_demo_sk = ss_cdemo_sk + AND cd_marital_status = 'M' + AND cd_education_status = 'Primary' + AND ss_sales_price BETWEEN 50.00 AND 100.00 + AND hd_dep_count = 1 ) + OR ( ss_hdemo_sk = hd_demo_sk + AND cd_demo_sk = ss_cdemo_sk + AND cd_marital_status = 'D' + AND cd_education_status = 'Secondary' + AND ss_sales_price BETWEEN 150.00 AND 200.00 + AND hd_dep_count = 1 ) ) + AND ( ( ss_addr_sk = ca_address_sk + AND ca_country = 'United States' + AND ca_state IN ( 'AZ', 'NE', 'IA' ) + AND ss_net_profit BETWEEN 100 AND 200 ) + OR ( ss_addr_sk = ca_address_sk + AND ca_country = 'United States' + AND ca_state IN ( 'MS', 'CA', 'NV' ) + AND ss_net_profit BETWEEN 150 AND 300 ) + OR ( ss_addr_sk = ca_address_sk + AND ca_country = 'United States' + AND ca_state IN ( 'GA', 'TX', 'NJ' ) + AND ss_net_profit BETWEEN 50 AND 250 ) ); diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query14.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query14.sql new file mode 100644 index 000000000..7a7572bba --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query14.sql @@ -0,0 +1,245 @@ +-- start query 14 in stream 0 using template query14.tpl +WITH cross_items + AS (SELECT i_item_sk ss_item_sk + FROM item, + (SELECT iss.i_brand_id brand_id, + iss.i_class_id class_id, + iss.i_category_id category_id + FROM store_sales, + item iss, + date_dim d1 + WHERE ss_item_sk = iss.i_item_sk + AND ss_sold_date_sk = d1.d_date_sk + AND d1.d_year BETWEEN 1999 AND 1999 + 2 + INTERSECT + SELECT ics.i_brand_id, + ics.i_class_id, + ics.i_category_id + FROM catalog_sales, + item ics, + date_dim d2 + WHERE cs_item_sk = ics.i_item_sk + AND cs_sold_date_sk = d2.d_date_sk + AND d2.d_year BETWEEN 1999 AND 1999 + 2 + INTERSECT + SELECT iws.i_brand_id, + iws.i_class_id, + iws.i_category_id + FROM web_sales, + item iws, + date_dim d3 + WHERE ws_item_sk = iws.i_item_sk + AND ws_sold_date_sk = d3.d_date_sk + AND d3.d_year BETWEEN 1999 AND 1999 + 2) + WHERE i_brand_id = brand_id + AND i_class_id = class_id + AND i_category_id = category_id), + avg_sales + AS (SELECT Avg(quantity * list_price) average_sales + FROM (SELECT ss_quantity quantity, + ss_list_price list_price + FROM store_sales, + date_dim + WHERE ss_sold_date_sk = d_date_sk + AND d_year BETWEEN 1999 AND 1999 + 2 + UNION ALL + SELECT cs_quantity quantity, + cs_list_price list_price + FROM catalog_sales, + date_dim + WHERE cs_sold_date_sk = d_date_sk + AND d_year BETWEEN 1999 AND 1999 + 2 + UNION ALL + SELECT ws_quantity quantity, + ws_list_price list_price + FROM web_sales, + date_dim + WHERE ws_sold_date_sk = d_date_sk + AND d_year BETWEEN 1999 AND 1999 + 2) x) +SELECT channel, + i_brand_id, + i_class_id, + i_category_id, + Sum(sales), + Sum(number_sales) +FROM (SELECT 'store' channel, + i_brand_id, + i_class_id, + i_category_id, + Sum(ss_quantity * ss_list_price) sales, + Count(*) number_sales + FROM store_sales, + item, + date_dim + WHERE ss_item_sk IN (SELECT ss_item_sk + FROM cross_items) + AND ss_item_sk = i_item_sk + AND ss_sold_date_sk = d_date_sk + AND d_year = 1999 + 2 + AND d_moy = 11 + GROUP BY i_brand_id, + i_class_id, + i_category_id + HAVING Sum(ss_quantity * ss_list_price) > (SELECT average_sales + FROM avg_sales) + UNION ALL + SELECT 'catalog' channel, + i_brand_id, + i_class_id, + i_category_id, + Sum(cs_quantity * cs_list_price) sales, + Count(*) number_sales + FROM catalog_sales, + item, + date_dim + WHERE cs_item_sk IN (SELECT ss_item_sk + FROM cross_items) + AND cs_item_sk = i_item_sk + AND cs_sold_date_sk = d_date_sk + AND d_year = 1999 + 2 + AND d_moy = 11 + GROUP BY i_brand_id, + i_class_id, + i_category_id + HAVING Sum(cs_quantity * cs_list_price) > (SELECT average_sales + FROM avg_sales) + UNION ALL + SELECT 'web' channel, + i_brand_id, + i_class_id, + i_category_id, + Sum(ws_quantity * ws_list_price) sales, + Count(*) number_sales + FROM web_sales, + item, + date_dim + WHERE ws_item_sk IN (SELECT ss_item_sk + FROM cross_items) + AND ws_item_sk = i_item_sk + AND ws_sold_date_sk = d_date_sk + AND d_year = 1999 + 2 + AND d_moy = 11 + GROUP BY i_brand_id, + i_class_id, + i_category_id + HAVING Sum(ws_quantity * ws_list_price) > (SELECT average_sales + FROM avg_sales)) y +GROUP BY rollup ( channel, i_brand_id, i_class_id, i_category_id ) +ORDER BY channel, + i_brand_id, + i_class_id, + i_category_id +LIMIT 100; + +WITH cross_items + AS (SELECT i_item_sk ss_item_sk + FROM item, + (SELECT iss.i_brand_id brand_id, + iss.i_class_id class_id, + iss.i_category_id category_id + FROM store_sales, + item iss, + date_dim d1 + WHERE ss_item_sk = iss.i_item_sk + AND ss_sold_date_sk = d1.d_date_sk + AND d1.d_year BETWEEN 1999 AND 1999 + 2 + INTERSECT + SELECT ics.i_brand_id, + ics.i_class_id, + ics.i_category_id + FROM catalog_sales, + item ics, + date_dim d2 + WHERE cs_item_sk = ics.i_item_sk + AND cs_sold_date_sk = d2.d_date_sk + AND d2.d_year BETWEEN 1999 AND 1999 + 2 + INTERSECT + SELECT iws.i_brand_id, + iws.i_class_id, + iws.i_category_id + FROM web_sales, + item iws, + date_dim d3 + WHERE ws_item_sk = iws.i_item_sk + AND ws_sold_date_sk = d3.d_date_sk + AND d3.d_year BETWEEN 1999 AND 1999 + 2) x + WHERE i_brand_id = brand_id + AND i_class_id = class_id + AND i_category_id = category_id), + avg_sales + AS (SELECT Avg(quantity * list_price) average_sales + FROM (SELECT ss_quantity quantity, + ss_list_price list_price + FROM store_sales, + date_dim + WHERE ss_sold_date_sk = d_date_sk + AND d_year BETWEEN 1999 AND 1999 + 2 + UNION ALL + SELECT cs_quantity quantity, + cs_list_price list_price + FROM catalog_sales, + date_dim + WHERE cs_sold_date_sk = d_date_sk + AND d_year BETWEEN 1999 AND 1999 + 2 + UNION ALL + SELECT ws_quantity quantity, + ws_list_price list_price + FROM web_sales, + date_dim + WHERE ws_sold_date_sk = d_date_sk + AND d_year BETWEEN 1999 AND 1999 + 2) x) +SELECT * +FROM (SELECT 'store' channel, + i_brand_id, + i_class_id, + i_category_id, + Sum(ss_quantity * ss_list_price) sales, + Count(*) number_sales + FROM store_sales, + item, + date_dim + WHERE ss_item_sk IN (SELECT ss_item_sk + FROM cross_items) + AND ss_item_sk = i_item_sk + AND ss_sold_date_sk = d_date_sk + AND d_week_seq = (SELECT d_week_seq + FROM date_dim + WHERE d_year = 1999 + 1 + AND d_moy = 12 + AND d_dom = 25) + GROUP BY i_brand_id, + i_class_id, + i_category_id + HAVING Sum(ss_quantity * ss_list_price) > (SELECT average_sales + FROM avg_sales)) this_year, + (SELECT 'store' channel, + i_brand_id, + i_class_id, + i_category_id, + Sum(ss_quantity * ss_list_price) sales, + Count(*) number_sales + FROM store_sales, + item, + date_dim + WHERE ss_item_sk IN (SELECT ss_item_sk + FROM cross_items) + AND ss_item_sk = i_item_sk + AND ss_sold_date_sk = d_date_sk + AND d_week_seq = (SELECT d_week_seq + FROM date_dim + WHERE d_year = 1999 + AND d_moy = 12 + AND d_dom = 25) + GROUP BY i_brand_id, + i_class_id, + i_category_id + HAVING Sum(ss_quantity * ss_list_price) > (SELECT average_sales + FROM avg_sales)) last_year +WHERE this_year.i_brand_id = last_year.i_brand_id + AND this_year.i_class_id = last_year.i_class_id + AND this_year.i_category_id = last_year.i_category_id +ORDER BY this_year.channel, + this_year.i_brand_id, + this_year.i_class_id, + this_year.i_category_id +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query15.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query15.sql new file mode 100644 index 000000000..207ddd719 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query15.sql @@ -0,0 +1,20 @@ +-- start query 15 in stream 0 using template query15.tpl +SELECT ca_zip, + Sum(cs_sales_price) +FROM catalog_sales, + customer, + customer_address, + date_dim +WHERE cs_bill_customer_sk = c_customer_sk + AND c_current_addr_sk = ca_address_sk + AND ( Substr(ca_zip, 1, 5) IN ( '85669', '86197', '88274', '83405', + '86475', '85392', '85460', '80348', + '81792' ) + OR ca_state IN ( 'CA', 'WA', 'GA' ) + OR cs_sales_price > 500 ) + AND cs_sold_date_sk = d_date_sk + AND d_qoy = 1 + AND d_year = 1998 +GROUP BY ca_zip +ORDER BY ca_zip +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query16.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query16.sql new file mode 100644 index 000000000..15149bfd6 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query16.sql @@ -0,0 +1,33 @@ +-- start query 16 in stream 0 using template query16.tpl +SELECT + Count(DISTINCT cs_order_number) AS "order count" , + Sum(cs_ext_ship_cost) AS "total shipping cost" , + Sum(cs_net_profit) AS "total net profit" +FROM catalog_sales cs1 , + date_dim , + customer_address , + call_center +WHERE d_date BETWEEN '2002-3-01' AND ( + Cast('2002-3-01' AS DATE) + INTERVAL '60' day) +AND cs1.cs_ship_date_sk = d_date_sk +AND cs1.cs_ship_addr_sk = ca_address_sk +AND ca_state = 'IA' +AND cs1.cs_call_center_sk = cc_call_center_sk +AND cc_county IN ('Williamson County', + 'Williamson County', + 'Williamson County', + 'Williamson County', + 'Williamson County' ) +AND EXISTS + ( + SELECT * + FROM catalog_sales cs2 + WHERE cs1.cs_order_number = cs2.cs_order_number + AND cs1.cs_warehouse_sk <> cs2.cs_warehouse_sk) +AND NOT EXISTS + ( + SELECT * + FROM catalog_returns cr1 + WHERE cs1.cs_order_number = cr1.cr_order_number) +ORDER BY count(DISTINCT cs_order_number) +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query17.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query17.sql new file mode 100644 index 000000000..da916d4a5 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query17.sql @@ -0,0 +1,56 @@ +-- start query 17 in stream 0 using template query17.tpl +SELECT i_item_id, + i_item_desc, + s_state, + Count(ss_quantity) AS + store_sales_quantitycount, + Avg(ss_quantity) AS + store_sales_quantityave, + Stddev_samp(ss_quantity) AS + store_sales_quantitystdev, + Stddev_samp(ss_quantity) / Avg(ss_quantity) AS + store_sales_quantitycov, + Count(sr_return_quantity) AS + store_returns_quantitycount, + Avg(sr_return_quantity) AS + store_returns_quantityave, + Stddev_samp(sr_return_quantity) AS + store_returns_quantitystdev, + Stddev_samp(sr_return_quantity) / Avg(sr_return_quantity) AS + store_returns_quantitycov, + Count(cs_quantity) AS + catalog_sales_quantitycount, + Avg(cs_quantity) AS + catalog_sales_quantityave, + Stddev_samp(cs_quantity) / Avg(cs_quantity) AS + catalog_sales_quantitystdev, + Stddev_samp(cs_quantity) / Avg(cs_quantity) AS + catalog_sales_quantitycov +FROM store_sales, + store_returns, + catalog_sales, + date_dim d1, + date_dim d2, + date_dim d3, + store, + item +WHERE d1.d_quarter_name = '1999Q1' + AND d1.d_date_sk = ss_sold_date_sk + AND i_item_sk = ss_item_sk + AND s_store_sk = ss_store_sk + AND ss_customer_sk = sr_customer_sk + AND ss_item_sk = sr_item_sk + AND ss_ticket_number = sr_ticket_number + AND sr_returned_date_sk = d2.d_date_sk + AND d2.d_quarter_name IN ( '1999Q1', '1999Q2', '1999Q3' ) + AND sr_customer_sk = cs_bill_customer_sk + AND sr_item_sk = cs_item_sk + AND cs_sold_date_sk = d3.d_date_sk + AND d3.d_quarter_name IN ( '1999Q1', '1999Q2', '1999Q3' ) +GROUP BY i_item_id, + i_item_desc, + s_state +ORDER BY i_item_id, + i_item_desc, + s_state +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query18.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query18.sql new file mode 100644 index 000000000..620cd2eb3 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query18.sql @@ -0,0 +1,38 @@ +-- start query 18 in stream 0 using template query18.tpl +SELECT i_item_id, + ca_country, + ca_state, + ca_county, + Avg(Cast(cs_quantity AS NUMERIC(12, 2))) agg1, + Avg(Cast(cs_list_price AS NUMERIC(12, 2))) agg2, + Avg(Cast(cs_coupon_amt AS NUMERIC(12, 2))) agg3, + Avg(Cast(cs_sales_price AS NUMERIC(12, 2))) agg4, + Avg(Cast(cs_net_profit AS NUMERIC(12, 2))) agg5, + Avg(Cast(c_birth_year AS NUMERIC(12, 2))) agg6, + Avg(Cast(cd1.cd_dep_count AS NUMERIC(12, 2))) agg7 +FROM catalog_sales, + customer_demographics cd1, + customer_demographics cd2, + customer, + customer_address, + date_dim, + item +WHERE cs_sold_date_sk = d_date_sk + AND cs_item_sk = i_item_sk + AND cs_bill_cdemo_sk = cd1.cd_demo_sk + AND cs_bill_customer_sk = c_customer_sk + AND cd1.cd_gender = 'F' + AND cd1.cd_education_status = 'Secondary' + AND c_current_cdemo_sk = cd2.cd_demo_sk + AND c_current_addr_sk = ca_address_sk + AND c_birth_month IN ( 8, 4, 2, 5, + 11, 9 ) + AND d_year = 2001 + AND ca_state IN ( 'KS', 'IA', 'AL', 'UT', + 'VA', 'NC', 'TX' ) +GROUP BY rollup ( i_item_id, ca_country, ca_state, ca_county ) +ORDER BY ca_country, + ca_state, + ca_county, + i_item_id +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query19.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query19.sql new file mode 100644 index 000000000..c3039b2fd --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query19.sql @@ -0,0 +1,31 @@ +-- start query 19 in stream 0 using template query19.tpl +SELECT i_brand_id brand_id, + i_brand brand, + i_manufact_id, + i_manufact, + Sum(ss_ext_sales_price) ext_price +FROM date_dim, + store_sales, + item, + customer, + customer_address, + store +WHERE d_date_sk = ss_sold_date_sk + AND ss_item_sk = i_item_sk + AND i_manager_id = 38 + AND d_moy = 12 + AND d_year = 1998 + AND ss_customer_sk = c_customer_sk + AND c_current_addr_sk = ca_address_sk + AND Substr(ca_zip, 1, 5) <> Substr(s_zip, 1, 5) + AND ss_store_sk = s_store_sk +GROUP BY i_brand, + i_brand_id, + i_manufact_id, + i_manufact +ORDER BY ext_price DESC, + i_brand, + i_brand_id, + i_manufact_id, + i_manufact +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query2.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query2.sql new file mode 100644 index 000000000..c85af4246 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query2.sql @@ -0,0 +1,79 @@ +-- start query 2 in stream 0 using template query2.tpl +WITH wscs + AS (SELECT sold_date_sk, + sales_price + FROM (SELECT ws_sold_date_sk sold_date_sk, + ws_ext_sales_price sales_price + FROM web_sales) + UNION ALL + (SELECT cs_sold_date_sk sold_date_sk, + cs_ext_sales_price sales_price + FROM catalog_sales)), + wswscs + AS (SELECT d_week_seq, + Sum(CASE + WHEN ( d_day_name = 'Sunday' ) THEN sales_price + ELSE NULL + END) sun_sales, + Sum(CASE + WHEN ( d_day_name = 'Monday' ) THEN sales_price + ELSE NULL + END) mon_sales, + Sum(CASE + WHEN ( d_day_name = 'Tuesday' ) THEN sales_price + ELSE NULL + END) tue_sales, + Sum(CASE + WHEN ( d_day_name = 'Wednesday' ) THEN sales_price + ELSE NULL + END) wed_sales, + Sum(CASE + WHEN ( d_day_name = 'Thursday' ) THEN sales_price + ELSE NULL + END) thu_sales, + Sum(CASE + WHEN ( d_day_name = 'Friday' ) THEN sales_price + ELSE NULL + END) fri_sales, + Sum(CASE + WHEN ( d_day_name = 'Saturday' ) THEN sales_price + ELSE NULL + END) sat_sales + FROM wscs, + date_dim + WHERE d_date_sk = sold_date_sk + GROUP BY d_week_seq) +SELECT d_week_seq1, + Round(sun_sales1 / sun_sales2, 2), + Round(mon_sales1 / mon_sales2, 2), + Round(tue_sales1 / tue_sales2, 2), + Round(wed_sales1 / wed_sales2, 2), + Round(thu_sales1 / thu_sales2, 2), + Round(fri_sales1 / fri_sales2, 2), + Round(sat_sales1 / sat_sales2, 2) +FROM (SELECT wswscs.d_week_seq d_week_seq1, + sun_sales sun_sales1, + mon_sales mon_sales1, + tue_sales tue_sales1, + wed_sales wed_sales1, + thu_sales thu_sales1, + fri_sales fri_sales1, + sat_sales sat_sales1 + FROM wswscs, + date_dim + WHERE date_dim.d_week_seq = wswscs.d_week_seq + AND d_year = 1998) y, + (SELECT wswscs.d_week_seq d_week_seq2, + sun_sales sun_sales2, + mon_sales mon_sales2, + tue_sales tue_sales2, + wed_sales wed_sales2, + thu_sales thu_sales2, + fri_sales fri_sales2, + sat_sales sat_sales2 + FROM wswscs, + date_dim + WHERE date_dim.d_week_seq = wswscs.d_week_seq + AND d_year = 1998 + 1) z +WHERE d_week_seq1 = d_week_seq2 - 53 +ORDER BY d_week_seq1; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query20.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query20.sql new file mode 100644 index 000000000..3c73340ea --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query20.sql @@ -0,0 +1,30 @@ +-- start query 20 in stream 0 using template query20.tpl +SELECT + i_item_id , + i_item_desc , + i_category , + i_class , + i_current_price , + Sum(cs_ext_sales_price) AS itemrevenue , + Sum(cs_ext_sales_price)*100/Sum(Sum(cs_ext_sales_price)) OVER (partition BY i_class) AS revenueratio +FROM catalog_sales , + item , + date_dim +WHERE cs_item_sk = i_item_sk +AND i_category IN ('Children', + 'Women', + 'Electronics') +AND cs_sold_date_sk = d_date_sk +AND d_date BETWEEN Cast('2001-02-03' AS DATE) AND ( + Cast('2001-02-03' AS DATE) + INTERVAL '30' day) +GROUP BY i_item_id , + i_item_desc , + i_category , + i_class , + i_current_price +ORDER BY i_category , + i_class , + i_item_id , + i_item_desc , + revenueratio +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query21.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query21.sql new file mode 100644 index 000000000..1811226a8 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query21.sql @@ -0,0 +1,38 @@ +-- start query 21 in stream 0 using template query21.tpl +SELECT + * +FROM ( + SELECT w_warehouse_name , + i_item_id , + Sum( + CASE + WHEN ( + Cast(d_date AS DATE) < Cast ('2000-05-13' AS DATE)) THEN inv_quantity_on_hand + ELSE 0 + END) AS inv_before , + Sum( + CASE + WHEN ( + Cast(d_date AS DATE) >= Cast ('2000-05-13' AS DATE)) THEN inv_quantity_on_hand + ELSE 0 + END) AS inv_after + FROM inventory , + warehouse , + item , + date_dim + WHERE i_current_price BETWEEN 0.99 AND 1.49 + AND i_item_sk = inv_item_sk + AND inv_warehouse_sk = w_warehouse_sk + AND inv_date_sk = d_date_sk + AND d_date BETWEEN (Cast ('2000-05-13' AS DATE) - INTERVAL '30' day) AND ( + cast ('2000-05-13' AS date) + INTERVAL '30' day) + GROUP BY w_warehouse_name, + i_item_id) x +WHERE ( + CASE + WHEN inv_before > 0 THEN inv_after / inv_before + ELSE NULL + END) BETWEEN 2.0/3.0 AND 3.0/2.0 +ORDER BY w_warehouse_name , + i_item_id +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query22.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query22.sql new file mode 100644 index 000000000..707fc7c85 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query22.sql @@ -0,0 +1,21 @@ +-- start query 22 in stream 0 using template query22.tpl +SELECT i_product_name, + i_brand, + i_class, + i_category, + Avg(inv_quantity_on_hand) qoh +FROM inventory, + date_dim, + item, + warehouse +WHERE inv_date_sk = d_date_sk + AND inv_item_sk = i_item_sk + AND inv_warehouse_sk = w_warehouse_sk + AND d_month_seq BETWEEN 1205 AND 1205 + 11 +GROUP BY rollup( i_product_name, i_brand, i_class, i_category ) +ORDER BY qoh, + i_product_name, + i_brand, + i_class, + i_category +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query23.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query23.sql new file mode 100644 index 000000000..66ffc4412 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query23.sql @@ -0,0 +1,136 @@ +-- start query 23 in stream 0 using template query23.tpl +WITH frequent_ss_items + AS (SELECT Substr(i_item_desc, 1, 30) itemdesc, + i_item_sk item_sk, + d_date solddate, + Count(*) cnt + FROM store_sales, + date_dim, + item + WHERE ss_sold_date_sk = d_date_sk + AND ss_item_sk = i_item_sk + AND d_year IN ( 1998, 1998 + 1, 1998 + 2, 1998 + 3 ) + GROUP BY Substr(i_item_desc, 1, 30), + i_item_sk, + d_date + HAVING Count(*) > 4), + max_store_sales + AS (SELECT Max(csales) tpcds_cmax + FROM (SELECT c_customer_sk, + Sum(ss_quantity * ss_sales_price) csales + FROM store_sales, + customer, + date_dim + WHERE ss_customer_sk = c_customer_sk + AND ss_sold_date_sk = d_date_sk + AND d_year IN ( 1998, 1998 + 1, 1998 + 2, 1998 + 3 ) + GROUP BY c_customer_sk)), + best_ss_customer + AS (SELECT c_customer_sk, + Sum(ss_quantity * ss_sales_price) ssales + FROM store_sales, + customer + WHERE ss_customer_sk = c_customer_sk + GROUP BY c_customer_sk + HAVING Sum(ss_quantity * ss_sales_price) > + ( 95 / 100.0 ) * (SELECT * + FROM max_store_sales)) +SELECT Sum(sales) +FROM (SELECT cs_quantity * cs_list_price sales + FROM catalog_sales, + date_dim + WHERE d_year = 1998 + AND d_moy = 6 + AND cs_sold_date_sk = d_date_sk + AND cs_item_sk IN (SELECT item_sk + FROM frequent_ss_items) + AND cs_bill_customer_sk IN (SELECT c_customer_sk + FROM best_ss_customer) + UNION ALL + SELECT ws_quantity * ws_list_price sales + FROM web_sales, + date_dim + WHERE d_year = 1998 + AND d_moy = 6 + AND ws_sold_date_sk = d_date_sk + AND ws_item_sk IN (SELECT item_sk + FROM frequent_ss_items) + AND ws_bill_customer_sk IN (SELECT c_customer_sk + FROM best_ss_customer)) LIMIT 100; + +WITH frequent_ss_items + AS (SELECT Substr(i_item_desc, 1, 30) itemdesc, + i_item_sk item_sk, + d_date solddate, + Count(*) cnt + FROM store_sales, + date_dim, + item + WHERE ss_sold_date_sk = d_date_sk + AND ss_item_sk = i_item_sk + AND d_year IN ( 1998, 1998 + 1, 1998 + 2, 1998 + 3 ) + GROUP BY Substr(i_item_desc, 1, 30), + i_item_sk, + d_date + HAVING Count(*) > 4), + max_store_sales + AS (SELECT Max(csales) tpcds_cmax + FROM (SELECT c_customer_sk, + Sum(ss_quantity * ss_sales_price) csales + FROM store_sales, + customer, + date_dim + WHERE ss_customer_sk = c_customer_sk + AND ss_sold_date_sk = d_date_sk + AND d_year IN ( 1998, 1998 + 1, 1998 + 2, 1998 + 3 ) + GROUP BY c_customer_sk)), + best_ss_customer + AS (SELECT c_customer_sk, + Sum(ss_quantity * ss_sales_price) ssales + FROM store_sales, + customer + WHERE ss_customer_sk = c_customer_sk + GROUP BY c_customer_sk + HAVING Sum(ss_quantity * ss_sales_price) > + ( 95 / 100.0 ) * (SELECT * + FROM max_store_sales)) +SELECT c_last_name, + c_first_name, + sales +FROM (SELECT c_last_name, + c_first_name, + Sum(cs_quantity * cs_list_price) sales + FROM catalog_sales, + customer, + date_dim + WHERE d_year = 1998 + AND d_moy = 6 + AND cs_sold_date_sk = d_date_sk + AND cs_item_sk IN (SELECT item_sk + FROM frequent_ss_items) + AND cs_bill_customer_sk IN (SELECT c_customer_sk + FROM best_ss_customer) + AND cs_bill_customer_sk = c_customer_sk + GROUP BY c_last_name, + c_first_name + UNION ALL + SELECT c_last_name, + c_first_name, + Sum(ws_quantity * ws_list_price) sales + FROM web_sales, + customer, + date_dim + WHERE d_year = 1998 + AND d_moy = 6 + AND ws_sold_date_sk = d_date_sk + AND ws_item_sk IN (SELECT item_sk + FROM frequent_ss_items) + AND ws_bill_customer_sk IN (SELECT c_customer_sk + FROM best_ss_customer) + AND ws_bill_customer_sk = c_customer_sk + GROUP BY c_last_name, + c_first_name) +ORDER BY c_last_name, + c_first_name, + sales +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query24.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query24.sql new file mode 100644 index 000000000..8382ca81d --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query24.sql @@ -0,0 +1,96 @@ +-- start query 24 in stream 0 using template query24.tpl +WITH ssales + AS (SELECT c_last_name, + c_first_name, + s_store_name, + ca_state, + s_state, + i_color, + i_current_price, + i_manager_id, + i_units, + i_size, + Sum(ss_net_profit) netpaid + FROM store_sales, + store_returns, + store, + item, + customer, + customer_address + WHERE ss_ticket_number = sr_ticket_number + AND ss_item_sk = sr_item_sk + AND ss_customer_sk = c_customer_sk + AND ss_item_sk = i_item_sk + AND ss_store_sk = s_store_sk + AND c_birth_country = Upper(ca_country) + AND s_zip = ca_zip + AND s_market_id = 6 + GROUP BY c_last_name, + c_first_name, + s_store_name, + ca_state, + s_state, + i_color, + i_current_price, + i_manager_id, + i_units, + i_size) +SELECT c_last_name, + c_first_name, + s_store_name, + Sum(netpaid) paid +FROM ssales +WHERE i_color = 'papaya' +GROUP BY c_last_name, + c_first_name, + s_store_name +HAVING Sum(netpaid) > (SELECT 0.05 * Avg(netpaid) + FROM ssales); + +WITH ssales + AS (SELECT c_last_name, + c_first_name, + s_store_name, + ca_state, + s_state, + i_color, + i_current_price, + i_manager_id, + i_units, + i_size, + Sum(ss_net_profit) netpaid + FROM store_sales, + store_returns, + store, + item, + customer, + customer_address + WHERE ss_ticket_number = sr_ticket_number + AND ss_item_sk = sr_item_sk + AND ss_customer_sk = c_customer_sk + AND ss_item_sk = i_item_sk + AND ss_store_sk = s_store_sk + AND c_birth_country = Upper(ca_country) + AND s_zip = ca_zip + AND s_market_id = 6 + GROUP BY c_last_name, + c_first_name, + s_store_name, + ca_state, + s_state, + i_color, + i_current_price, + i_manager_id, + i_units, + i_size) +SELECT c_last_name, + c_first_name, + s_store_name, + Sum(netpaid) paid +FROM ssales +WHERE i_color = 'chartreuse' +GROUP BY c_last_name, + c_first_name, + s_store_name +HAVING Sum(netpaid) > (SELECT 0.05 * Avg(netpaid) + FROM ssales); diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query25.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query25.sql new file mode 100644 index 000000000..fe58702f6 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query25.sql @@ -0,0 +1,41 @@ +-- start query 25 in stream 0 using template query25.tpl +SELECT i_item_id, + i_item_desc, + s_store_id, + s_store_name, + Max(ss_net_profit) AS store_sales_profit, + Max(sr_net_loss) AS store_returns_loss, + Max(cs_net_profit) AS catalog_sales_profit +FROM store_sales, + store_returns, + catalog_sales, + date_dim d1, + date_dim d2, + date_dim d3, + store, + item +WHERE d1.d_moy = 4 + AND d1.d_year = 2001 + AND d1.d_date_sk = ss_sold_date_sk + AND i_item_sk = ss_item_sk + AND s_store_sk = ss_store_sk + AND ss_customer_sk = sr_customer_sk + AND ss_item_sk = sr_item_sk + AND ss_ticket_number = sr_ticket_number + AND sr_returned_date_sk = d2.d_date_sk + AND d2.d_moy BETWEEN 4 AND 10 + AND d2.d_year = 2001 + AND sr_customer_sk = cs_bill_customer_sk + AND sr_item_sk = cs_item_sk + AND cs_sold_date_sk = d3.d_date_sk + AND d3.d_moy BETWEEN 4 AND 10 + AND d3.d_year = 2001 +GROUP BY i_item_id, + i_item_desc, + s_store_id, + s_store_name +ORDER BY i_item_id, + i_item_desc, + s_store_id, + s_store_name +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query26.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query26.sql new file mode 100644 index 000000000..d4818a37b --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query26.sql @@ -0,0 +1,24 @@ +-- start query 26 in stream 0 using template query26.tpl +SELECT i_item_id, + Avg(cs_quantity) agg1, + Avg(cs_list_price) agg2, + Avg(cs_coupon_amt) agg3, + Avg(cs_sales_price) agg4 +FROM catalog_sales, + customer_demographics, + date_dim, + item, + promotion +WHERE cs_sold_date_sk = d_date_sk + AND cs_item_sk = i_item_sk + AND cs_bill_cdemo_sk = cd_demo_sk + AND cs_promo_sk = p_promo_sk + AND cd_gender = 'F' + AND cd_marital_status = 'W' + AND cd_education_status = 'Secondary' + AND ( p_channel_email = 'N' + OR p_channel_event = 'N' ) + AND d_year = 2000 +GROUP BY i_item_id +ORDER BY i_item_id +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query27.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query27.sql new file mode 100644 index 000000000..98fe056e5 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query27.sql @@ -0,0 +1,27 @@ +-- start query 27 in stream 0 using template query27.tpl +SELECT i_item_id, + s_state, + Grouping(s_state) g_state, + Avg(ss_quantity) agg1, + Avg(ss_list_price) agg2, + Avg(ss_coupon_amt) agg3, + Avg(ss_sales_price) agg4 +FROM store_sales, + customer_demographics, + date_dim, + store, + item +WHERE ss_sold_date_sk = d_date_sk + AND ss_item_sk = i_item_sk + AND ss_store_sk = s_store_sk + AND ss_cdemo_sk = cd_demo_sk + AND cd_gender = 'M' + AND cd_marital_status = 'D' + AND cd_education_status = 'College' + AND d_year = 2000 + AND s_state IN ( 'TN', 'TN', 'TN', 'TN', + 'TN', 'TN' ) +GROUP BY rollup ( i_item_id, s_state ) +ORDER BY i_item_id, + s_state +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query28.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query28.sql new file mode 100644 index 000000000..3aa74a9d9 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query28.sql @@ -0,0 +1,51 @@ +-- start query 28 in stream 0 using template query28.tpl +SELECT * +FROM (SELECT Avg(ss_list_price) B1_LP, + Count(ss_list_price) B1_CNT, + Count(DISTINCT ss_list_price) B1_CNTD + FROM store_sales + WHERE ss_quantity BETWEEN 0 AND 5 + AND ( ss_list_price BETWEEN 18 AND 18 + 10 + OR ss_coupon_amt BETWEEN 1939 AND 1939 + 1000 + OR ss_wholesale_cost BETWEEN 34 AND 34 + 20 )) B1, + (SELECT Avg(ss_list_price) B2_LP, + Count(ss_list_price) B2_CNT, + Count(DISTINCT ss_list_price) B2_CNTD + FROM store_sales + WHERE ss_quantity BETWEEN 6 AND 10 + AND ( ss_list_price BETWEEN 1 AND 1 + 10 + OR ss_coupon_amt BETWEEN 35 AND 35 + 1000 + OR ss_wholesale_cost BETWEEN 50 AND 50 + 20 )) B2, + (SELECT Avg(ss_list_price) B3_LP, + Count(ss_list_price) B3_CNT, + Count(DISTINCT ss_list_price) B3_CNTD + FROM store_sales + WHERE ss_quantity BETWEEN 11 AND 15 + AND ( ss_list_price BETWEEN 91 AND 91 + 10 + OR ss_coupon_amt BETWEEN 1412 AND 1412 + 1000 + OR ss_wholesale_cost BETWEEN 17 AND 17 + 20 )) B3, + (SELECT Avg(ss_list_price) B4_LP, + Count(ss_list_price) B4_CNT, + Count(DISTINCT ss_list_price) B4_CNTD + FROM store_sales + WHERE ss_quantity BETWEEN 16 AND 20 + AND ( ss_list_price BETWEEN 9 AND 9 + 10 + OR ss_coupon_amt BETWEEN 5270 AND 5270 + 1000 + OR ss_wholesale_cost BETWEEN 29 AND 29 + 20 )) B4, + (SELECT Avg(ss_list_price) B5_LP, + Count(ss_list_price) B5_CNT, + Count(DISTINCT ss_list_price) B5_CNTD + FROM store_sales + WHERE ss_quantity BETWEEN 21 AND 25 + AND ( ss_list_price BETWEEN 45 AND 45 + 10 + OR ss_coupon_amt BETWEEN 826 AND 826 + 1000 + OR ss_wholesale_cost BETWEEN 5 AND 5 + 20 )) B5, + (SELECT Avg(ss_list_price) B6_LP, + Count(ss_list_price) B6_CNT, + Count(DISTINCT ss_list_price) B6_CNTD + FROM store_sales + WHERE ss_quantity BETWEEN 26 AND 30 + AND ( ss_list_price BETWEEN 174 AND 174 + 10 + OR ss_coupon_amt BETWEEN 5548 AND 5548 + 1000 + OR ss_wholesale_cost BETWEEN 42 AND 42 + 20 )) B6 +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query29.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query29.sql new file mode 100644 index 000000000..b685e6179 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query29.sql @@ -0,0 +1,40 @@ +-- start query 29 in stream 0 using template query29.tpl +SELECT i_item_id, + i_item_desc, + s_store_id, + s_store_name, + Avg(ss_quantity) AS store_sales_quantity, + Avg(sr_return_quantity) AS store_returns_quantity, + Avg(cs_quantity) AS catalog_sales_quantity +FROM store_sales, + store_returns, + catalog_sales, + date_dim d1, + date_dim d2, + date_dim d3, + store, + item +WHERE d1.d_moy = 4 + AND d1.d_year = 1998 + AND d1.d_date_sk = ss_sold_date_sk + AND i_item_sk = ss_item_sk + AND s_store_sk = ss_store_sk + AND ss_customer_sk = sr_customer_sk + AND ss_item_sk = sr_item_sk + AND ss_ticket_number = sr_ticket_number + AND sr_returned_date_sk = d2.d_date_sk + AND d2.d_moy BETWEEN 4 AND 4 + 3 + AND d2.d_year = 1998 + AND sr_customer_sk = cs_bill_customer_sk + AND sr_item_sk = cs_item_sk + AND cs_sold_date_sk = d3.d_date_sk + AND d3.d_year IN ( 1998, 1998 + 1, 1998 + 2 ) +GROUP BY i_item_id, + i_item_desc, + s_store_id, + s_store_name +ORDER BY i_item_id, + i_item_desc, + s_store_id, + s_store_name +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query3.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query3.sql new file mode 100644 index 000000000..711b0fe7c --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query3.sql @@ -0,0 +1,19 @@ +-- start query 3 in stream 0 using template query3.tpl +SELECT dt.d_year, + item.i_brand_id brand_id, + item.i_brand brand, + Sum(ss_ext_discount_amt) sum_agg +FROM date_dim dt, + store_sales, + item +WHERE dt.d_date_sk = store_sales.ss_sold_date_sk + AND store_sales.ss_item_sk = item.i_item_sk + AND item.i_manufact_id = 427 + AND dt.d_moy = 11 +GROUP BY dt.d_year, + item.i_brand, + item.i_brand_id +ORDER BY dt.d_year, + sum_agg DESC, + brand_id +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query30.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query30.sql new file mode 100644 index 000000000..4b0498f72 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query30.sql @@ -0,0 +1,49 @@ +-- start query 30 in stream 0 using template query30.tpl +WITH customer_total_return + AS (SELECT wr_returning_customer_sk AS ctr_customer_sk, + ca_state AS ctr_state, + Sum(wr_return_amt) AS ctr_total_return + FROM web_returns, + date_dim, + customer_address + WHERE wr_returned_date_sk = d_date_sk + AND d_year = 2000 + AND wr_returning_addr_sk = ca_address_sk + GROUP BY wr_returning_customer_sk, + ca_state) +SELECT c_customer_id, + c_salutation, + c_first_name, + c_last_name, + c_preferred_cust_flag, + c_birth_day, + c_birth_month, + c_birth_year, + c_birth_country, + c_login, + c_email_address, + c_last_review_date, + ctr_total_return +FROM customer_total_return ctr1, + customer_address, + customer +WHERE ctr1.ctr_total_return > (SELECT Avg(ctr_total_return) * 1.2 + FROM customer_total_return ctr2 + WHERE ctr1.ctr_state = ctr2.ctr_state) + AND ca_address_sk = c_current_addr_sk + AND ca_state = 'IN' + AND ctr1.ctr_customer_sk = c_customer_sk +ORDER BY c_customer_id, + c_salutation, + c_first_name, + c_last_name, + c_preferred_cust_flag, + c_birth_day, + c_birth_month, + c_birth_year, + c_birth_country, + c_login, + c_email_address, + c_last_review_date, + ctr_total_return +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query31.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query31.sql new file mode 100644 index 000000000..8ab3ffb41 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query31.sql @@ -0,0 +1,73 @@ +-- start query 31 in stream 0 using template query31.tpl +WITH ss + AS (SELECT ca_county, + d_qoy, + d_year, + Sum(ss_ext_sales_price) AS store_sales + FROM store_sales, + date_dim, + customer_address + WHERE ss_sold_date_sk = d_date_sk + AND ss_addr_sk = ca_address_sk + GROUP BY ca_county, + d_qoy, + d_year), + ws + AS (SELECT ca_county, + d_qoy, + d_year, + Sum(ws_ext_sales_price) AS web_sales + FROM web_sales, + date_dim, + customer_address + WHERE ws_sold_date_sk = d_date_sk + AND ws_bill_addr_sk = ca_address_sk + GROUP BY ca_county, + d_qoy, + d_year) +SELECT ss1.ca_county, + ss1.d_year, + ws2.web_sales / ws1.web_sales web_q1_q2_increase, + ss2.store_sales / ss1.store_sales store_q1_q2_increase, + ws3.web_sales / ws2.web_sales web_q2_q3_increase, + ss3.store_sales / ss2.store_sales store_q2_q3_increase +FROM ss ss1, + ss ss2, + ss ss3, + ws ws1, + ws ws2, + ws ws3 +WHERE ss1.d_qoy = 1 + AND ss1.d_year = 2001 + AND ss1.ca_county = ss2.ca_county + AND ss2.d_qoy = 2 + AND ss2.d_year = 2001 + AND ss2.ca_county = ss3.ca_county + AND ss3.d_qoy = 3 + AND ss3.d_year = 2001 + AND ss1.ca_county = ws1.ca_county + AND ws1.d_qoy = 1 + AND ws1.d_year = 2001 + AND ws1.ca_county = ws2.ca_county + AND ws2.d_qoy = 2 + AND ws2.d_year = 2001 + AND ws1.ca_county = ws3.ca_county + AND ws3.d_qoy = 3 + AND ws3.d_year = 2001 + AND CASE + WHEN ws1.web_sales > 0 THEN ws2.web_sales / ws1.web_sales + ELSE NULL + END > CASE + WHEN ss1.store_sales > 0 THEN + ss2.store_sales / ss1.store_sales + ELSE NULL + END + AND CASE + WHEN ws2.web_sales > 0 THEN ws3.web_sales / ws2.web_sales + ELSE NULL + END > CASE + WHEN ss2.store_sales > 0 THEN + ss3.store_sales / ss2.store_sales + ELSE NULL + END +ORDER BY ss1.d_year; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query32.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query32.sql new file mode 100644 index 000000000..22b66886a --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query32.sql @@ -0,0 +1,21 @@ +-- start query 32 in stream 0 using template query32.tpl +SELECT + Sum(cs_ext_discount_amt) AS "excess discount amount" +FROM catalog_sales , + item , + date_dim +WHERE i_manufact_id = 610 +AND i_item_sk = cs_item_sk +AND d_date BETWEEN '2001-03-04' AND ( + Cast('2001-03-04' AS DATE) + INTERVAL '90' day) +AND d_date_sk = cs_sold_date_sk +AND cs_ext_discount_amt > + ( + SELECT 1.3 * avg(cs_ext_discount_amt) + FROM catalog_sales , + date_dim + WHERE cs_item_sk = i_item_sk + AND d_date BETWEEN '2001-03-04' AND ( + cast('2001-03-04' AS date) + INTERVAL '90' day) + AND d_date_sk = cs_sold_date_sk ) +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query33.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query33.sql new file mode 100644 index 000000000..c4161054a --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query33.sql @@ -0,0 +1,65 @@ +-- start query 33 in stream 0 using template query33.tpl +WITH ss + AS (SELECT i_manufact_id, + Sum(ss_ext_sales_price) total_sales + FROM store_sales, + date_dim, + customer_address, + item + WHERE i_manufact_id IN (SELECT i_manufact_id + FROM item + WHERE i_category IN ( 'Books' )) + AND ss_item_sk = i_item_sk + AND ss_sold_date_sk = d_date_sk + AND d_year = 1999 + AND d_moy = 3 + AND ss_addr_sk = ca_address_sk + AND ca_gmt_offset = -5 + GROUP BY i_manufact_id), + cs + AS (SELECT i_manufact_id, + Sum(cs_ext_sales_price) total_sales + FROM catalog_sales, + date_dim, + customer_address, + item + WHERE i_manufact_id IN (SELECT i_manufact_id + FROM item + WHERE i_category IN ( 'Books' )) + AND cs_item_sk = i_item_sk + AND cs_sold_date_sk = d_date_sk + AND d_year = 1999 + AND d_moy = 3 + AND cs_bill_addr_sk = ca_address_sk + AND ca_gmt_offset = -5 + GROUP BY i_manufact_id), + ws + AS (SELECT i_manufact_id, + Sum(ws_ext_sales_price) total_sales + FROM web_sales, + date_dim, + customer_address, + item + WHERE i_manufact_id IN (SELECT i_manufact_id + FROM item + WHERE i_category IN ( 'Books' )) + AND ws_item_sk = i_item_sk + AND ws_sold_date_sk = d_date_sk + AND d_year = 1999 + AND d_moy = 3 + AND ws_bill_addr_sk = ca_address_sk + AND ca_gmt_offset = -5 + GROUP BY i_manufact_id) +SELECT i_manufact_id, + Sum(total_sales) total_sales +FROM (SELECT * + FROM ss + UNION ALL + SELECT * + FROM cs + UNION ALL + SELECT * + FROM ws) tmp1 +GROUP BY i_manufact_id +ORDER BY total_sales +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query34.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query34.sql new file mode 100644 index 000000000..613734ffa --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query34.sql @@ -0,0 +1,46 @@ +-- start query 34 in stream 0 using template query34.tpl +SELECT c_last_name, + c_first_name, + c_salutation, + c_preferred_cust_flag, + ss_ticket_number, + cnt +FROM (SELECT ss_ticket_number, + ss_customer_sk, + Count(*) cnt + FROM store_sales, + date_dim, + store, + household_demographics + WHERE store_sales.ss_sold_date_sk = date_dim.d_date_sk + AND store_sales.ss_store_sk = store.s_store_sk + AND store_sales.ss_hdemo_sk = household_demographics.hd_demo_sk + AND ( date_dim.d_dom BETWEEN 1 AND 3 + OR date_dim.d_dom BETWEEN 25 AND 28 ) + AND ( household_demographics.hd_buy_potential = '>10000' + OR household_demographics.hd_buy_potential = 'unknown' ) + AND household_demographics.hd_vehicle_count > 0 + AND ( CASE + WHEN household_demographics.hd_vehicle_count > 0 THEN + household_demographics.hd_dep_count / + household_demographics.hd_vehicle_count + ELSE NULL + END ) > 1.2 + AND date_dim.d_year IN ( 1999, 1999 + 1, 1999 + 2 ) + AND store.s_county IN ( 'Williamson County', 'Williamson County', + 'Williamson County', + 'Williamson County' + , + 'Williamson County', 'Williamson County', + 'Williamson County', + 'Williamson County' + ) + GROUP BY ss_ticket_number, + ss_customer_sk) dn, + customer +WHERE ss_customer_sk = c_customer_sk + AND cnt BETWEEN 15 AND 20 +ORDER BY c_last_name, + c_first_name, + c_salutation, + c_preferred_cust_flag DESC; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query35.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query35.sql new file mode 100644 index 000000000..d5912a411 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query35.sql @@ -0,0 +1,58 @@ +-- start query 35 in stream 0 using template query35.tpl +SELECT ca_state, + cd_gender, + cd_marital_status, + cd_dep_count, + Count(*) cnt1, + Stddev_samp(cd_dep_count), + Avg(cd_dep_count), + Max(cd_dep_count), + cd_dep_employed_count, + Count(*) cnt2, + Stddev_samp(cd_dep_employed_count), + Avg(cd_dep_employed_count), + Max(cd_dep_employed_count), + cd_dep_college_count, + Count(*) cnt3, + Stddev_samp(cd_dep_college_count), + Avg(cd_dep_college_count), + Max(cd_dep_college_count) +FROM customer c, + customer_address ca, + customer_demographics +WHERE c.c_current_addr_sk = ca.ca_address_sk + AND cd_demo_sk = c.c_current_cdemo_sk + AND EXISTS (SELECT * + FROM store_sales, + date_dim + WHERE c.c_customer_sk = ss_customer_sk + AND ss_sold_date_sk = d_date_sk + AND d_year = 2001 + AND d_qoy < 4) + AND ( EXISTS (SELECT * + FROM web_sales, + date_dim + WHERE c.c_customer_sk = ws_bill_customer_sk + AND ws_sold_date_sk = d_date_sk + AND d_year = 2001 + AND d_qoy < 4) + OR EXISTS (SELECT * + FROM catalog_sales, + date_dim + WHERE c.c_customer_sk = cs_ship_customer_sk + AND cs_sold_date_sk = d_date_sk + AND d_year = 2001 + AND d_qoy < 4) ) +GROUP BY ca_state, + cd_gender, + cd_marital_status, + cd_dep_count, + cd_dep_employed_count, + cd_dep_college_count +ORDER BY ca_state, + cd_gender, + cd_marital_status, + cd_dep_count, + cd_dep_employed_count, + cd_dep_college_count +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query36.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query36.sql new file mode 100644 index 000000000..21d69fbbb --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query36.sql @@ -0,0 +1,31 @@ +-- start query 36 in stream 0 using template query36.tpl +SELECT Sum(ss_net_profit) / Sum(ss_ext_sales_price) AS + gross_margin, + i_category, + i_class, + Grouping(i_category) + Grouping(i_class) AS + lochierarchy, + Rank() + OVER ( + partition BY Grouping(i_category)+Grouping(i_class), CASE + WHEN Grouping( + i_class) = 0 THEN i_category END + ORDER BY Sum(ss_net_profit)/Sum(ss_ext_sales_price) ASC) AS + rank_within_parent +FROM store_sales, + date_dim d1, + item, + store +WHERE d1.d_year = 2000 + AND d1.d_date_sk = ss_sold_date_sk + AND i_item_sk = ss_item_sk + AND s_store_sk = ss_store_sk + AND s_state IN ( 'TN', 'TN', 'TN', 'TN', + 'TN', 'TN', 'TN', 'TN' ) +GROUP BY rollup( i_category, i_class ) +ORDER BY lochierarchy DESC, + CASE + WHEN lochierarchy = 0 THEN i_category + END, + rank_within_parent +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query37.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query37.sql new file mode 100644 index 000000000..52cd153a9 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query37.sql @@ -0,0 +1,22 @@ +-- start query 37 in stream 0 using template query37.tpl +SELECT + i_item_id , + i_item_desc , + i_current_price +FROM item, + inventory, + date_dim, + catalog_sales +WHERE i_current_price BETWEEN 20 AND 20 + 30 +AND inv_item_sk = i_item_sk +AND d_date_sk=inv_date_sk +AND d_date BETWEEN Cast('1999-03-06' AS DATE) AND ( + Cast('1999-03-06' AS DATE) + INTERVAL '60' day) +AND i_manufact_id IN (843,815,850,840) +AND inv_quantity_on_hand BETWEEN 100 AND 500 +AND cs_item_sk = i_item_sk +GROUP BY i_item_id, + i_item_desc, + i_current_price +ORDER BY i_item_id +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query38.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query38.sql new file mode 100644 index 000000000..546068717 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query38.sql @@ -0,0 +1,32 @@ +-- start query 38 in stream 0 using template query38.tpl +SELECT Count(*) +FROM (SELECT DISTINCT c_last_name, + c_first_name, + d_date + FROM store_sales, + date_dim, + customer + WHERE store_sales.ss_sold_date_sk = date_dim.d_date_sk + AND store_sales.ss_customer_sk = customer.c_customer_sk + AND d_month_seq BETWEEN 1188 AND 1188 + 11 + INTERSECT + SELECT DISTINCT c_last_name, + c_first_name, + d_date + FROM catalog_sales, + date_dim, + customer + WHERE catalog_sales.cs_sold_date_sk = date_dim.d_date_sk + AND catalog_sales.cs_bill_customer_sk = customer.c_customer_sk + AND d_month_seq BETWEEN 1188 AND 1188 + 11 + INTERSECT + SELECT DISTINCT c_last_name, + c_first_name, + d_date + FROM web_sales, + date_dim, + customer + WHERE web_sales.ws_sold_date_sk = date_dim.d_date_sk + AND web_sales.ws_bill_customer_sk = customer.c_customer_sk + AND d_month_seq BETWEEN 1188 AND 1188 + 11) hot_cust +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query39.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query39.sql new file mode 100644 index 000000000..4abb67d30 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query39.sql @@ -0,0 +1,117 @@ +-- start query 39 in stream 0 using template query39.tpl +WITH inv + AS (SELECT w_warehouse_name, + w_warehouse_sk, + i_item_sk, + d_moy, + stdev, + mean, + CASE mean + WHEN 0 THEN NULL + ELSE stdev / mean + END cov + FROM (SELECT w_warehouse_name, + w_warehouse_sk, + i_item_sk, + d_moy, + Stddev_samp(inv_quantity_on_hand) stdev, + Avg(inv_quantity_on_hand) mean + FROM inventory, + item, + warehouse, + date_dim + WHERE inv_item_sk = i_item_sk + AND inv_warehouse_sk = w_warehouse_sk + AND inv_date_sk = d_date_sk + AND d_year = 2002 + GROUP BY w_warehouse_name, + w_warehouse_sk, + i_item_sk, + d_moy) foo + WHERE CASE mean + WHEN 0 THEN 0 + ELSE stdev / mean + END > 1) +SELECT inv1.w_warehouse_sk, + inv1.i_item_sk, + inv1.d_moy, + inv1.mean, + inv1.cov, + inv2.w_warehouse_sk, + inv2.i_item_sk, + inv2.d_moy, + inv2.mean, + inv2.cov +FROM inv inv1, + inv inv2 +WHERE inv1.i_item_sk = inv2.i_item_sk + AND inv1.w_warehouse_sk = inv2.w_warehouse_sk + AND inv1.d_moy = 1 + AND inv2.d_moy = 1 + 1 +ORDER BY inv1.w_warehouse_sk, + inv1.i_item_sk, + inv1.d_moy, + inv1.mean, + inv1.cov, + inv2.d_moy, + inv2.mean, + inv2.cov; + +WITH inv + AS (SELECT w_warehouse_name, + w_warehouse_sk, + i_item_sk, + d_moy, + stdev, + mean, + CASE mean + WHEN 0 THEN NULL + ELSE stdev / mean + END cov + FROM (SELECT w_warehouse_name, + w_warehouse_sk, + i_item_sk, + d_moy, + Stddev_samp(inv_quantity_on_hand) stdev, + Avg(inv_quantity_on_hand) mean + FROM inventory, + item, + warehouse, + date_dim + WHERE inv_item_sk = i_item_sk + AND inv_warehouse_sk = w_warehouse_sk + AND inv_date_sk = d_date_sk + AND d_year = 2002 + GROUP BY w_warehouse_name, + w_warehouse_sk, + i_item_sk, + d_moy) foo + WHERE CASE mean + WHEN 0 THEN 0 + ELSE stdev / mean + END > 1) +SELECT inv1.w_warehouse_sk, + inv1.i_item_sk, + inv1.d_moy, + inv1.mean, + inv1.cov, + inv2.w_warehouse_sk, + inv2.i_item_sk, + inv2.d_moy, + inv2.mean, + inv2.cov +FROM inv inv1, + inv inv2 +WHERE inv1.i_item_sk = inv2.i_item_sk + AND inv1.w_warehouse_sk = inv2.w_warehouse_sk + AND inv1.d_moy = 1 + AND inv2.d_moy = 1 + 1 + AND inv1.cov > 1.5 +ORDER BY inv1.w_warehouse_sk, + inv1.i_item_sk, + inv1.d_moy, + inv1.mean, + inv1.cov, + inv2.d_moy, + inv2.mean, + inv2.cov; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query4.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query4.sql new file mode 100644 index 000000000..281b2d5d2 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query4.sql @@ -0,0 +1,152 @@ +-- start query 4 in stream 0 using template query4.tpl +WITH year_total + AS (SELECT c_customer_id customer_id, + c_first_name customer_first_name, + c_last_name customer_last_name, + c_preferred_cust_flag customer_preferred_cust_flag + , + c_birth_country + customer_birth_country, + c_login customer_login, + c_email_address customer_email_address, + d_year dyear, + Sum(( ( ss_ext_list_price - ss_ext_wholesale_cost + - ss_ext_discount_amt + ) + + + ss_ext_sales_price ) / 2) year_total, + 's' sale_type + FROM customer, + store_sales, + date_dim + WHERE c_customer_sk = ss_customer_sk + AND ss_sold_date_sk = d_date_sk + GROUP BY c_customer_id, + c_first_name, + c_last_name, + c_preferred_cust_flag, + c_birth_country, + c_login, + c_email_address, + d_year + UNION ALL + SELECT c_customer_id customer_id, + c_first_name customer_first_name, + c_last_name customer_last_name, + c_preferred_cust_flag + customer_preferred_cust_flag, + c_birth_country customer_birth_country + , + c_login + customer_login, + c_email_address customer_email_address + , + d_year dyear + , + Sum(( ( ( cs_ext_list_price + - cs_ext_wholesale_cost + - cs_ext_discount_amt + ) + + cs_ext_sales_price ) / 2 )) year_total, + 'c' sale_type + FROM customer, + catalog_sales, + date_dim + WHERE c_customer_sk = cs_bill_customer_sk + AND cs_sold_date_sk = d_date_sk + GROUP BY c_customer_id, + c_first_name, + c_last_name, + c_preferred_cust_flag, + c_birth_country, + c_login, + c_email_address, + d_year + UNION ALL + SELECT c_customer_id customer_id, + c_first_name customer_first_name, + c_last_name customer_last_name, + c_preferred_cust_flag + customer_preferred_cust_flag, + c_birth_country customer_birth_country + , + c_login + customer_login, + c_email_address customer_email_address + , + d_year dyear + , + Sum(( ( ( ws_ext_list_price + - ws_ext_wholesale_cost + - ws_ext_discount_amt + ) + + ws_ext_sales_price ) / 2 )) year_total, + 'w' sale_type + FROM customer, + web_sales, + date_dim + WHERE c_customer_sk = ws_bill_customer_sk + AND ws_sold_date_sk = d_date_sk + GROUP BY c_customer_id, + c_first_name, + c_last_name, + c_preferred_cust_flag, + c_birth_country, + c_login, + c_email_address, + d_year) +SELECT t_s_secyear.customer_id, + t_s_secyear.customer_first_name, + t_s_secyear.customer_last_name, + t_s_secyear.customer_preferred_cust_flag +FROM year_total t_s_firstyear, + year_total t_s_secyear, + year_total t_c_firstyear, + year_total t_c_secyear, + year_total t_w_firstyear, + year_total t_w_secyear +WHERE t_s_secyear.customer_id = t_s_firstyear.customer_id + AND t_s_firstyear.customer_id = t_c_secyear.customer_id + AND t_s_firstyear.customer_id = t_c_firstyear.customer_id + AND t_s_firstyear.customer_id = t_w_firstyear.customer_id + AND t_s_firstyear.customer_id = t_w_secyear.customer_id + AND t_s_firstyear.sale_type = 's' + AND t_c_firstyear.sale_type = 'c' + AND t_w_firstyear.sale_type = 'w' + AND t_s_secyear.sale_type = 's' + AND t_c_secyear.sale_type = 'c' + AND t_w_secyear.sale_type = 'w' + AND t_s_firstyear.dyear = 2001 + AND t_s_secyear.dyear = 2001 + 1 + AND t_c_firstyear.dyear = 2001 + AND t_c_secyear.dyear = 2001 + 1 + AND t_w_firstyear.dyear = 2001 + AND t_w_secyear.dyear = 2001 + 1 + AND t_s_firstyear.year_total > 0 + AND t_c_firstyear.year_total > 0 + AND t_w_firstyear.year_total > 0 + AND CASE + WHEN t_c_firstyear.year_total > 0 THEN t_c_secyear.year_total / + t_c_firstyear.year_total + ELSE NULL + END > CASE + WHEN t_s_firstyear.year_total > 0 THEN + t_s_secyear.year_total / + t_s_firstyear.year_total + ELSE NULL + END + AND CASE + WHEN t_c_firstyear.year_total > 0 THEN t_c_secyear.year_total / + t_c_firstyear.year_total + ELSE NULL + END > CASE + WHEN t_w_firstyear.year_total > 0 THEN + t_w_secyear.year_total / + t_w_firstyear.year_total + ELSE NULL + END +ORDER BY t_s_secyear.customer_id, + t_s_secyear.customer_first_name, + t_s_secyear.customer_last_name, + t_s_secyear.customer_preferred_cust_flag +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query40.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query40.sql new file mode 100644 index 000000000..f7f84b873 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query40.sql @@ -0,0 +1,35 @@ +-- start query 40 in stream 0 using template query40.tpl +SELECT + w_state , + i_item_id , + Sum( + CASE + WHEN ( + Cast(d_date AS DATE) < Cast ('2002-06-01' AS DATE)) THEN cs_sales_price - COALESCE(cr_refunded_cash,0) + ELSE 0 + END) AS sales_before , + Sum( + CASE + WHEN ( + Cast(d_date AS DATE) >= Cast ('2002-06-01' AS DATE)) THEN cs_sales_price - COALESCE(cr_refunded_cash,0) + ELSE 0 + END) AS sales_after +FROM catalog_sales +LEFT OUTER JOIN catalog_returns +ON ( + cs_order_number = cr_order_number + AND cs_item_sk = cr_item_sk) , + warehouse , + item , + date_dim +WHERE i_current_price BETWEEN 0.99 AND 1.49 +AND i_item_sk = cs_item_sk +AND cs_warehouse_sk = w_warehouse_sk +AND cs_sold_date_sk = d_date_sk +AND d_date BETWEEN (Cast ('2002-06-01' AS DATE) - INTERVAL '30' day) AND ( + cast ('2002-06-01' AS date) + INTERVAL '30' day) +GROUP BY w_state, + i_item_id +ORDER BY w_state, + i_item_id +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query41.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query41.sql new file mode 100644 index 000000000..467ed35ae --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query41.sql @@ -0,0 +1,66 @@ +-- start query 41 in stream 0 using template query41.tpl +SELECT Distinct(i_product_name) +FROM item i1 +WHERE i_manufact_id BETWEEN 765 AND 765 + 40 + AND (SELECT Count(*) AS item_cnt + FROM item + WHERE ( i_manufact = i1.i_manufact + AND ( ( i_category = 'Women' + AND ( i_color = 'dim' + OR i_color = 'green' ) + AND ( i_units = 'Gross' + OR i_units = 'Dozen' ) + AND ( i_size = 'economy' + OR i_size = 'petite' ) ) + OR ( i_category = 'Women' + AND ( i_color = 'navajo' + OR i_color = 'aquamarine' ) + AND ( i_units = 'Case' + OR i_units = 'Unknown' ) + AND ( i_size = 'large' + OR i_size = 'N/A' ) ) + OR ( i_category = 'Men' + AND ( i_color = 'indian' + OR i_color = 'dark' ) + AND ( i_units = 'Oz' + OR i_units = 'Lb' ) + AND ( i_size = 'extra large' + OR i_size = 'small' ) ) + OR ( i_category = 'Men' + AND ( i_color = 'peach' + OR i_color = 'purple' ) + AND ( i_units = 'Tbl' + OR i_units = 'Bunch' ) + AND ( i_size = 'economy' + OR i_size = 'petite' ) ) ) ) + OR ( i_manufact = i1.i_manufact + AND ( ( i_category = 'Women' + AND ( i_color = 'orchid' + OR i_color = 'peru' ) + AND ( i_units = 'Carton' + OR i_units = 'Cup' ) + AND ( i_size = 'economy' + OR i_size = 'petite' ) ) + OR ( i_category = 'Women' + AND ( i_color = 'violet' + OR i_color = 'papaya' ) + AND ( i_units = 'Ounce' + OR i_units = 'Box' ) + AND ( i_size = 'large' + OR i_size = 'N/A' ) ) + OR ( i_category = 'Men' + AND ( i_color = 'drab' + OR i_color = 'grey' ) + AND ( i_units = 'Each' + OR i_units = 'N/A' ) + AND ( i_size = 'extra large' + OR i_size = 'small' ) ) + OR ( i_category = 'Men' + AND ( i_color = 'chocolate' + OR i_color = 'antique' ) + AND ( i_units = 'Dram' + OR i_units = 'Gram' ) + AND ( i_size = 'economy' + OR i_size = 'petite' ) ) ) )) > 0 +ORDER BY i_product_name +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query42.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query42.sql new file mode 100644 index 000000000..706d3c6f1 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query42.sql @@ -0,0 +1,21 @@ +-- start query 42 in stream 0 using template query42.tpl +SELECT dt.d_year, + item.i_category_id, + item.i_category, + Sum(ss_ext_sales_price) +FROM date_dim dt, + store_sales, + item +WHERE dt.d_date_sk = store_sales.ss_sold_date_sk + AND store_sales.ss_item_sk = item.i_item_sk + AND item.i_manager_id = 1 + AND dt.d_moy = 12 + AND dt.d_year = 2000 +GROUP BY dt.d_year, + item.i_category_id, + item.i_category +ORDER BY Sum(ss_ext_sales_price) DESC, + dt.d_year, + item.i_category_id, + item.i_category +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query43.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query43.sql new file mode 100644 index 000000000..e7624a19c --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query43.sql @@ -0,0 +1,50 @@ +-- start query 43 in stream 0 using template query43.tpl +SELECT s_store_name, + s_store_id, + Sum(CASE + WHEN ( d_day_name = 'Sunday' ) THEN ss_sales_price + ELSE NULL + END) sun_sales, + Sum(CASE + WHEN ( d_day_name = 'Monday' ) THEN ss_sales_price + ELSE NULL + END) mon_sales, + Sum(CASE + WHEN ( d_day_name = 'Tuesday' ) THEN ss_sales_price + ELSE NULL + END) tue_sales, + Sum(CASE + WHEN ( d_day_name = 'Wednesday' ) THEN ss_sales_price + ELSE NULL + END) wed_sales, + Sum(CASE + WHEN ( d_day_name = 'Thursday' ) THEN ss_sales_price + ELSE NULL + END) thu_sales, + Sum(CASE + WHEN ( d_day_name = 'Friday' ) THEN ss_sales_price + ELSE NULL + END) fri_sales, + Sum(CASE + WHEN ( d_day_name = 'Saturday' ) THEN ss_sales_price + ELSE NULL + END) sat_sales +FROM date_dim, + store_sales, + store +WHERE d_date_sk = ss_sold_date_sk + AND s_store_sk = ss_store_sk + AND s_gmt_offset = -5 + AND d_year = 2002 +GROUP BY s_store_name, + s_store_id +ORDER BY s_store_name, + s_store_id, + sun_sales, + mon_sales, + tue_sales, + wed_sales, + thu_sales, + fri_sales, + sat_sales +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query44.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query44.sql new file mode 100644 index 000000000..ba869d3ce --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query44.sql @@ -0,0 +1,51 @@ +-- start query 44 in stream 0 using template query44.tpl +SELECT asceding.rnk, + i1.i_product_name best_performing, + i2.i_product_name worst_performing +FROM (SELECT * + FROM (SELECT item_sk, + Rank() + OVER ( + ORDER BY rank_col ASC) rnk + FROM (SELECT ss_item_sk item_sk, + Avg(ss_net_profit) rank_col + FROM store_sales ss1 + WHERE ss_store_sk = 4 + GROUP BY ss_item_sk + HAVING Avg(ss_net_profit) > 0.9 * + (SELECT Avg(ss_net_profit) + rank_col + FROM store_sales + WHERE ss_store_sk = 4 + AND ss_cdemo_sk IS + NULL + GROUP BY ss_store_sk))V1) + V11 + WHERE rnk < 11) asceding, + (SELECT * + FROM (SELECT item_sk, + Rank() + OVER ( + ORDER BY rank_col DESC) rnk + FROM (SELECT ss_item_sk item_sk, + Avg(ss_net_profit) rank_col + FROM store_sales ss1 + WHERE ss_store_sk = 4 + GROUP BY ss_item_sk + HAVING Avg(ss_net_profit) > 0.9 * + (SELECT Avg(ss_net_profit) + rank_col + FROM store_sales + WHERE ss_store_sk = 4 + AND ss_cdemo_sk IS + NULL + GROUP BY ss_store_sk))V2) + V21 + WHERE rnk < 11) descending, + item i1, + item i2 +WHERE asceding.rnk = descending.rnk + AND i1.i_item_sk = asceding.item_sk + AND i2.i_item_sk = descending.item_sk +ORDER BY asceding.rnk +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query45.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query45.sql new file mode 100644 index 000000000..e8d35c1c0 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query45.sql @@ -0,0 +1,28 @@ +-- start query 45 in stream 0 using template query45.tpl +SELECT ca_zip, + ca_state, + Sum(ws_sales_price) +FROM web_sales, + customer, + customer_address, + date_dim, + item +WHERE ws_bill_customer_sk = c_customer_sk + AND c_current_addr_sk = ca_address_sk + AND ws_item_sk = i_item_sk + AND ( Substr(ca_zip, 1, 5) IN ( '85669', '86197', '88274', '83405', + '86475', '85392', '85460', '80348', + '81792' ) + OR i_item_id IN (SELECT i_item_id + FROM item + WHERE i_item_sk IN ( 2, 3, 5, 7, + 11, 13, 17, 19, + 23, 29 )) ) + AND ws_sold_date_sk = d_date_sk + AND d_qoy = 1 + AND d_year = 2000 +GROUP BY ca_zip, + ca_state +ORDER BY ca_zip, + ca_state +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query46.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query46.sql new file mode 100644 index 000000000..b88b36400 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query46.sql @@ -0,0 +1,44 @@ +-- start query 46 in stream 0 using template query46.tpl +SELECT c_last_name, + c_first_name, + ca_city, + bought_city, + ss_ticket_number, + amt, + profit +FROM (SELECT ss_ticket_number, + ss_customer_sk, + ca_city bought_city, + Sum(ss_coupon_amt) amt, + Sum(ss_net_profit) profit + FROM store_sales, + date_dim, + store, + household_demographics, + customer_address + WHERE store_sales.ss_sold_date_sk = date_dim.d_date_sk + AND store_sales.ss_store_sk = store.s_store_sk + AND store_sales.ss_hdemo_sk = household_demographics.hd_demo_sk + AND store_sales.ss_addr_sk = customer_address.ca_address_sk + AND ( household_demographics.hd_dep_count = 6 + OR household_demographics.hd_vehicle_count = 0 ) + AND date_dim.d_dow IN ( 6, 0 ) + AND date_dim.d_year IN ( 2000, 2000 + 1, 2000 + 2 ) + AND store.s_city IN ( 'Midway', 'Fairview', 'Fairview', + 'Fairview', + 'Fairview' ) + GROUP BY ss_ticket_number, + ss_customer_sk, + ss_addr_sk, + ca_city) dn, + customer, + customer_address current_addr +WHERE ss_customer_sk = c_customer_sk + AND customer.c_current_addr_sk = current_addr.ca_address_sk + AND current_addr.ca_city <> bought_city +ORDER BY c_last_name, + c_first_name, + ca_city, + bought_city, + ss_ticket_number +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query47.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query47.sql new file mode 100644 index 000000000..36ddcbbcb --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query47.sql @@ -0,0 +1,72 @@ +-- start query 47 in stream 0 using template query47.tpl +WITH v1 + AS (SELECT i_category, + i_brand, + s_store_name, + s_company_name, + d_year, + d_moy, + Sum(ss_sales_price) sum_sales, + Avg(Sum(ss_sales_price)) + OVER ( + partition BY i_category, i_brand, s_store_name, + s_company_name, + d_year) + avg_monthly_sales, + Rank() + OVER ( + partition BY i_category, i_brand, s_store_name, + s_company_name + ORDER BY d_year, d_moy) rn + FROM item, + store_sales, + date_dim, + store + WHERE ss_item_sk = i_item_sk + AND ss_sold_date_sk = d_date_sk + AND ss_store_sk = s_store_sk + AND ( d_year = 1999 + OR ( d_year = 1999 - 1 + AND d_moy = 12 ) + OR ( d_year = 1999 + 1 + AND d_moy = 1 ) ) + GROUP BY i_category, + i_brand, + s_store_name, + s_company_name, + d_year, + d_moy), + v2 + AS (SELECT v1.i_category, + v1.d_year, + v1.d_moy, + v1.avg_monthly_sales, + v1.sum_sales, + v1_lag.sum_sales psum, + v1_lead.sum_sales nsum + FROM v1, + v1 v1_lag, + v1 v1_lead + WHERE v1.i_category = v1_lag.i_category + AND v1.i_category = v1_lead.i_category + AND v1.i_brand = v1_lag.i_brand + AND v1.i_brand = v1_lead.i_brand + AND v1.s_store_name = v1_lag.s_store_name + AND v1.s_store_name = v1_lead.s_store_name + AND v1.s_company_name = v1_lag.s_company_name + AND v1.s_company_name = v1_lead.s_company_name + AND v1.rn = v1_lag.rn + 1 + AND v1.rn = v1_lead.rn - 1) +SELECT * +FROM v2 +WHERE d_year = 1999 + AND avg_monthly_sales > 0 + AND CASE + WHEN avg_monthly_sales > 0 THEN Abs(sum_sales - avg_monthly_sales) + / + avg_monthly_sales + ELSE NULL + END > 0.1 +ORDER BY sum_sales - avg_monthly_sales, + 3 +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query48.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query48.sql new file mode 100644 index 000000000..aa8375382 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query48.sql @@ -0,0 +1,34 @@ +-- start query 48 in stream 0 using template query48.tpl +SELECT Sum (ss_quantity) +FROM store_sales, + store, + customer_demographics, + customer_address, + date_dim +WHERE s_store_sk = ss_store_sk + AND ss_sold_date_sk = d_date_sk + AND d_year = 1999 + AND ( ( cd_demo_sk = ss_cdemo_sk + AND cd_marital_status = 'W' + AND cd_education_status = 'Secondary' + AND ss_sales_price BETWEEN 100.00 AND 150.00 ) + OR ( cd_demo_sk = ss_cdemo_sk + AND cd_marital_status = 'M' + AND cd_education_status = 'Advanced Degree' + AND ss_sales_price BETWEEN 50.00 AND 100.00 ) + OR ( cd_demo_sk = ss_cdemo_sk + AND cd_marital_status = 'D' + AND cd_education_status = '2 yr Degree' + AND ss_sales_price BETWEEN 150.00 AND 200.00 ) ) + AND ( ( ss_addr_sk = ca_address_sk + AND ca_country = 'United States' + AND ca_state IN ( 'TX', 'NE', 'MO' ) + AND ss_net_profit BETWEEN 0 AND 2000 ) + OR ( ss_addr_sk = ca_address_sk + AND ca_country = 'United States' + AND ca_state IN ( 'CO', 'TN', 'ND' ) + AND ss_net_profit BETWEEN 150 AND 3000 ) + OR ( ss_addr_sk = ca_address_sk + AND ca_country = 'United States' + AND ca_state IN ( 'OK', 'PA', 'CA' ) + AND ss_net_profit BETWEEN 50 AND 25000 ) ); diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query49.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query49.sql new file mode 100644 index 000000000..bade54bc0 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query49.sql @@ -0,0 +1,133 @@ +-- start query 49 in stream 0 using template query49.tpl +SELECT 'web' AS channel, + web.item, + web.return_ratio, + web.return_rank, + web.currency_rank +FROM (SELECT item, + return_ratio, + currency_ratio, + Rank() + OVER ( + ORDER BY return_ratio) AS return_rank, + Rank() + OVER ( + ORDER BY currency_ratio) AS currency_rank + FROM (SELECT ws.ws_item_sk AS + item, + ( Cast(Sum(COALESCE(wr.wr_return_quantity, 0)) AS DEC(15, + 4)) / + Cast( + Sum(COALESCE(ws.ws_quantity, 0)) AS DEC(15, 4)) ) AS + return_ratio, + ( Cast(Sum(COALESCE(wr.wr_return_amt, 0)) AS DEC(15, 4)) + / Cast( + Sum( + COALESCE(ws.ws_net_paid, 0)) AS DEC(15, + 4)) ) AS + currency_ratio + FROM web_sales ws + LEFT OUTER JOIN web_returns wr + ON ( ws.ws_order_number = wr.wr_order_number + AND ws.ws_item_sk = wr.wr_item_sk ), + date_dim + WHERE wr.wr_return_amt > 10000 + AND ws.ws_net_profit > 1 + AND ws.ws_net_paid > 0 + AND ws.ws_quantity > 0 + AND ws_sold_date_sk = d_date_sk + AND d_year = 1999 + AND d_moy = 12 + GROUP BY ws.ws_item_sk) in_web) web +WHERE ( web.return_rank <= 10 + OR web.currency_rank <= 10 ) +UNION +SELECT 'catalog' AS channel, + catalog.item, + catalog.return_ratio, + catalog.return_rank, + catalog.currency_rank +FROM (SELECT item, + return_ratio, + currency_ratio, + Rank() + OVER ( + ORDER BY return_ratio) AS return_rank, + Rank() + OVER ( + ORDER BY currency_ratio) AS currency_rank + FROM (SELECT cs.cs_item_sk AS + item, + ( Cast(Sum(COALESCE(cr.cr_return_quantity, 0)) AS DEC(15, + 4)) / + Cast( + Sum(COALESCE(cs.cs_quantity, 0)) AS DEC(15, 4)) ) AS + return_ratio, + ( Cast(Sum(COALESCE(cr.cr_return_amount, 0)) AS DEC(15, 4 + )) / + Cast(Sum( + COALESCE(cs.cs_net_paid, 0)) AS DEC( + 15, 4)) ) AS + currency_ratio + FROM catalog_sales cs + LEFT OUTER JOIN catalog_returns cr + ON ( cs.cs_order_number = cr.cr_order_number + AND cs.cs_item_sk = cr.cr_item_sk ), + date_dim + WHERE cr.cr_return_amount > 10000 + AND cs.cs_net_profit > 1 + AND cs.cs_net_paid > 0 + AND cs.cs_quantity > 0 + AND cs_sold_date_sk = d_date_sk + AND d_year = 1999 + AND d_moy = 12 + GROUP BY cs.cs_item_sk) in_cat) catalog +WHERE ( catalog.return_rank <= 10 + OR catalog.currency_rank <= 10 ) +UNION +SELECT 'store' AS channel, + store.item, + store.return_ratio, + store.return_rank, + store.currency_rank +FROM (SELECT item, + return_ratio, + currency_ratio, + Rank() + OVER ( + ORDER BY return_ratio) AS return_rank, + Rank() + OVER ( + ORDER BY currency_ratio) AS currency_rank + FROM (SELECT sts.ss_item_sk AS + item, + ( Cast(Sum(COALESCE(sr.sr_return_quantity, 0)) AS DEC(15, + 4)) / + Cast( + Sum(COALESCE(sts.ss_quantity, 0)) AS DEC(15, 4)) ) AS + return_ratio, + ( Cast(Sum(COALESCE(sr.sr_return_amt, 0)) AS DEC(15, 4)) + / Cast( + Sum( + COALESCE(sts.ss_net_paid, 0)) AS DEC(15, 4)) ) AS + currency_ratio + FROM store_sales sts + LEFT OUTER JOIN store_returns sr + ON ( sts.ss_ticket_number = + sr.sr_ticket_number + AND sts.ss_item_sk = sr.sr_item_sk ), + date_dim + WHERE sr.sr_return_amt > 10000 + AND sts.ss_net_profit > 1 + AND sts.ss_net_paid > 0 + AND sts.ss_quantity > 0 + AND ss_sold_date_sk = d_date_sk + AND d_year = 1999 + AND d_moy = 12 + GROUP BY sts.ss_item_sk) in_store) store +WHERE ( store.return_rank <= 10 + OR store.currency_rank <= 10 ) +ORDER BY 1, + 4, + 5 +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query5.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query5.sql new file mode 100644 index 000000000..0c03b0ddc --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query5.sql @@ -0,0 +1,127 @@ +-- start query 5 in stream 0 using template query5.tpl +WITH ssr AS +( + SELECT s_store_id, + Sum(sales_price) AS sales, + Sum(profit) AS profit, + Sum(return_amt) AS returns1, + Sum(net_loss) AS profit_loss + FROM ( + SELECT ss_store_sk AS store_sk, + ss_sold_date_sk AS date_sk, + ss_ext_sales_price AS sales_price, + ss_net_profit AS profit, + Cast(0 AS DECIMAL(7,2)) AS return_amt, + Cast(0 AS DECIMAL(7,2)) AS net_loss + FROM store_sales + UNION ALL + SELECT sr_store_sk AS store_sk, + sr_returned_date_sk AS date_sk, + Cast(0 AS DECIMAL(7,2)) AS sales_price, + Cast(0 AS DECIMAL(7,2)) AS profit, + sr_return_amt AS return_amt, + sr_net_loss AS net_loss + FROM store_returns ) salesreturns, + date_dim, + store + WHERE date_sk = d_date_sk + AND d_date BETWEEN Cast('2002-08-22' AS DATE) AND ( + Cast('2002-08-22' AS DATE) + INTERVAL '14' day) + AND store_sk = s_store_sk + GROUP BY s_store_id) , csr AS +( + SELECT cp_catalog_page_id, + sum(sales_price) AS sales, + sum(profit) AS profit, + sum(return_amt) AS returns1, + sum(net_loss) AS profit_loss + FROM ( + SELECT cs_catalog_page_sk AS page_sk, + cs_sold_date_sk AS date_sk, + cs_ext_sales_price AS sales_price, + cs_net_profit AS profit, + cast(0 AS decimal(7,2)) AS return_amt, + cast(0 AS decimal(7,2)) AS net_loss + FROM catalog_sales + UNION ALL + SELECT cr_catalog_page_sk AS page_sk, + cr_returned_date_sk AS date_sk, + cast(0 AS decimal(7,2)) AS sales_price, + cast(0 AS decimal(7,2)) AS profit, + cr_return_amount AS return_amt, + cr_net_loss AS net_loss + FROM catalog_returns ) salesreturns, + date_dim, + catalog_page + WHERE date_sk = d_date_sk + AND d_date BETWEEN cast('2002-08-22' AS date) AND ( + cast('2002-08-22' AS date) + INTERVAL '14' day) + AND page_sk = cp_catalog_page_sk + GROUP BY cp_catalog_page_id) , wsr AS +( + SELECT web_site_id, + sum(sales_price) AS sales, + sum(profit) AS profit, + sum(return_amt) AS returns1, + sum(net_loss) AS profit_loss + FROM ( + SELECT ws_web_site_sk AS wsr_web_site_sk, + ws_sold_date_sk AS date_sk, + ws_ext_sales_price AS sales_price, + ws_net_profit AS profit, + cast(0 AS decimal(7,2)) AS return_amt, + cast(0 AS decimal(7,2)) AS net_loss + FROM web_sales + UNION ALL + SELECT ws_web_site_sk AS wsr_web_site_sk, + wr_returned_date_sk AS date_sk, + cast(0 AS decimal(7,2)) AS sales_price, + cast(0 AS decimal(7,2)) AS profit, + wr_return_amt AS return_amt, + wr_net_loss AS net_loss + FROM web_returns + LEFT OUTER JOIN web_sales + ON ( + wr_item_sk = ws_item_sk + AND wr_order_number = ws_order_number) ) salesreturns, + date_dim, + web_site + WHERE date_sk = d_date_sk + AND d_date BETWEEN cast('2002-08-22' AS date) AND ( + cast('2002-08-22' AS date) + INTERVAL '14' day) + AND wsr_web_site_sk = web_site_sk + GROUP BY web_site_id) +SELECT + channel , + id , + sum(sales) AS sales , + sum(returns1) AS returns1 , + sum(profit) AS profit +FROM ( + SELECT 'store channel' AS channel , + 'store' + || s_store_id AS id , + sales , + returns1 , + (profit - profit_loss) AS profit + FROM ssr + UNION ALL + SELECT 'catalog channel' AS channel , + 'catalog_page' + || cp_catalog_page_id AS id , + sales , + returns1 , + (profit - profit_loss) AS profit + FROM csr + UNION ALL + SELECT 'web channel' AS channel , + 'web_site' + || web_site_id AS id , + sales , + returns1 , + (profit - profit_loss) AS profit + FROM wsr ) x +GROUP BY rollup (channel, id) +ORDER BY channel , + id +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query50.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query50.sql new file mode 100644 index 000000000..a4c108469 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query50.sql @@ -0,0 +1,71 @@ +-- start query 50 in stream 0 using template query50.tpl +SELECT s_store_name, + s_company_id, + s_street_number, + s_street_name, + s_street_type, + s_suite_number, + s_city, + s_county, + s_state, + s_zip, + Sum(CASE + WHEN ( sr_returned_date_sk - ss_sold_date_sk <= 30 ) THEN 1 + ELSE 0 + END) AS "30 days", + Sum(CASE + WHEN ( sr_returned_date_sk - ss_sold_date_sk > 30 ) + AND ( sr_returned_date_sk - ss_sold_date_sk <= 60 ) + THEN 1 + ELSE 0 + END) AS "31-60 days", + Sum(CASE + WHEN ( sr_returned_date_sk - ss_sold_date_sk > 60 ) + AND ( sr_returned_date_sk - ss_sold_date_sk <= 90 ) + THEN 1 + ELSE 0 + END) AS "61-90 days", + Sum(CASE + WHEN ( sr_returned_date_sk - ss_sold_date_sk > 90 ) + AND ( sr_returned_date_sk - ss_sold_date_sk <= 120 ) + THEN 1 + ELSE 0 + END) AS "91-120 days", + Sum(CASE + WHEN ( sr_returned_date_sk - ss_sold_date_sk > 120 ) THEN 1 + ELSE 0 + END) AS ">120 days" +FROM store_sales, + store_returns, + store, + date_dim d1, + date_dim d2 +WHERE d2.d_year = 2002 + AND d2.d_moy = 9 + AND ss_ticket_number = sr_ticket_number + AND ss_item_sk = sr_item_sk + AND ss_sold_date_sk = d1.d_date_sk + AND sr_returned_date_sk = d2.d_date_sk + AND ss_customer_sk = sr_customer_sk + AND ss_store_sk = s_store_sk +GROUP BY s_store_name, + s_company_id, + s_street_number, + s_street_name, + s_street_type, + s_suite_number, + s_city, + s_county, + s_state, + s_zip +ORDER BY s_store_name, + s_company_id, + s_street_number, + s_street_name, + s_street_type, + s_suite_number, + s_city, + s_county, + s_state, + s_zip +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query51.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query51.sql new file mode 100644 index 000000000..2c73e7d60 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query51.sql @@ -0,0 +1,54 @@ +-- start query 51 in stream 0 using template query51.tpl +WITH web_v1 AS +( + SELECT ws_item_sk item_sk, + d_date, + sum(Sum(ws_sales_price)) OVER (partition BY ws_item_sk ORDER BY d_date rows BETWEEN UNBOUNDED PRECEDING AND CURRENT row) cume_sales + FROM web_sales , + date_dim + WHERE ws_sold_date_sk=d_date_sk + AND d_month_seq BETWEEN 1192 AND 1192+11 + AND ws_item_sk IS NOT NULL + GROUP BY ws_item_sk, + d_date), store_v1 AS +( + SELECT ss_item_sk item_sk, + d_date, + sum(sum(ss_sales_price)) OVER (partition BY ss_item_sk ORDER BY d_date rows BETWEEN UNBOUNDED PRECEDING AND CURRENT row) cume_sales + FROM store_sales , + date_dim + WHERE ss_sold_date_sk=d_date_sk + AND d_month_seq BETWEEN 1192 AND 1192+11 + AND ss_item_sk IS NOT NULL + GROUP BY ss_item_sk, + d_date) +SELECT + * +FROM ( + SELECT item_sk , + d_date , + web_sales , + store_sales , + max(web_sales) OVER (partition BY item_sk ORDER BY d_date rows BETWEEN UNBOUNDED PRECEDING AND CURRENT row) web_cumulative , + max(store_sales) OVER (partition BY item_sk ORDER BY d_date rows BETWEEN UNBOUNDED PRECEDING AND CURRENT row) store_cumulative + FROM ( + SELECT + CASE + WHEN web.item_sk IS NOT NULL THEN web.item_sk + ELSE store.item_sk + END item_sk , + CASE + WHEN web.d_date IS NOT NULL THEN web.d_date + ELSE store.d_date + END d_date , + web.cume_sales web_sales , + store.cume_sales store_sales + FROM web_v1 web + FULL OUTER JOIN store_v1 store + ON ( + web.item_sk = store.item_sk + AND web.d_date = store.d_date) )x )y +WHERE web_cumulative > store_cumulative +ORDER BY item_sk , + d_date +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query52.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query52.sql new file mode 100644 index 000000000..065f3f169 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query52.sql @@ -0,0 +1,20 @@ +-- start query 52 in stream 0 using template query52.tpl +SELECT dt.d_year, + item.i_brand_id brand_id, + item.i_brand brand, + Sum(ss_ext_sales_price) ext_price +FROM date_dim dt, + store_sales, + item +WHERE dt.d_date_sk = store_sales.ss_sold_date_sk + AND store_sales.ss_item_sk = item.i_item_sk + AND item.i_manager_id = 1 + AND dt.d_moy = 11 + AND dt.d_year = 1999 +GROUP BY dt.d_year, + item.i_brand, + item.i_brand_id +ORDER BY dt.d_year, + ext_price DESC, + brand_id +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query53.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query53.sql new file mode 100644 index 000000000..4c0452a32 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query53.sql @@ -0,0 +1,46 @@ +-- start query 53 in stream 0 using template query53.tpl +SELECT * +FROM (SELECT i_manufact_id, + Sum(ss_sales_price) sum_sales, + Avg(Sum(ss_sales_price)) + OVER ( + partition BY i_manufact_id) avg_quarterly_sales + FROM item, + store_sales, + date_dim, + store + WHERE ss_item_sk = i_item_sk + AND ss_sold_date_sk = d_date_sk + AND ss_store_sk = s_store_sk + AND d_month_seq IN ( 1199, 1199 + 1, 1199 + 2, 1199 + 3, + 1199 + 4, 1199 + 5, 1199 + 6, 1199 + 7, + 1199 + 8, 1199 + 9, 1199 + 10, 1199 + 11 ) + AND ( ( i_category IN ( 'Books', 'Children', 'Electronics' ) + AND i_class IN ( 'personal', 'portable', 'reference', + 'self-help' ) + AND i_brand IN ( 'scholaramalgamalg #14', + 'scholaramalgamalg #7' + , + 'exportiunivamalg #9', + 'scholaramalgamalg #9' ) + ) + OR ( i_category IN ( 'Women', 'Music', 'Men' ) + AND i_class IN ( 'accessories', 'classical', + 'fragrances', + 'pants' ) + AND i_brand IN ( 'amalgimporto #1', + 'edu packscholar #1', + 'exportiimporto #1', + 'importoamalg #1' ) ) ) + GROUP BY i_manufact_id, + d_qoy) tmp1 +WHERE CASE + WHEN avg_quarterly_sales > 0 THEN Abs (sum_sales - avg_quarterly_sales) + / + avg_quarterly_sales + ELSE NULL + END > 0.1 +ORDER BY avg_quarterly_sales, + sum_sales, + i_manufact_id +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query54.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query54.sql new file mode 100644 index 000000000..b776c9590 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query54.sql @@ -0,0 +1,57 @@ +-- start query 54 in stream 0 using template query54.tpl +WITH my_customers + AS (SELECT DISTINCT c_customer_sk, + c_current_addr_sk + FROM (SELECT cs_sold_date_sk sold_date_sk, + cs_bill_customer_sk customer_sk, + cs_item_sk item_sk + FROM catalog_sales + UNION ALL + SELECT ws_sold_date_sk sold_date_sk, + ws_bill_customer_sk customer_sk, + ws_item_sk item_sk + FROM web_sales) cs_or_ws_sales, + item, + date_dim, + customer + WHERE sold_date_sk = d_date_sk + AND item_sk = i_item_sk + AND i_category = 'Sports' + AND i_class = 'fitness' + AND c_customer_sk = cs_or_ws_sales.customer_sk + AND d_moy = 5 + AND d_year = 2000), + my_revenue + AS (SELECT c_customer_sk, + Sum(ss_ext_sales_price) AS revenue + FROM my_customers, + store_sales, + customer_address, + store, + date_dim + WHERE c_current_addr_sk = ca_address_sk + AND ca_county = s_county + AND ca_state = s_state + AND ss_sold_date_sk = d_date_sk + AND c_customer_sk = ss_customer_sk + AND d_month_seq BETWEEN (SELECT DISTINCT d_month_seq + 1 + FROM date_dim + WHERE d_year = 2000 + AND d_moy = 5) AND + (SELECT DISTINCT + d_month_seq + 3 + FROM date_dim + WHERE d_year = 2000 + AND d_moy = 5) + GROUP BY c_customer_sk), + segments + AS (SELECT Cast(( revenue / 50 ) AS INT) AS segment + FROM my_revenue) +SELECT segment, + Count(*) AS num_customers, + segment * 50 AS segment_base +FROM segments +GROUP BY segment +ORDER BY segment, + num_customers +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query55.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query55.sql new file mode 100644 index 000000000..37ed2563f --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query55.sql @@ -0,0 +1,17 @@ +-- start query 55 in stream 0 using template query55.tpl +SELECT i_brand_id brand_id, + i_brand brand, + Sum(ss_ext_sales_price) ext_price +FROM date_dim, + store_sales, + item +WHERE d_date_sk = ss_sold_date_sk + AND ss_item_sk = i_item_sk + AND i_manager_id = 33 + AND d_moy = 12 + AND d_year = 1998 +GROUP BY i_brand, + i_brand_id +ORDER BY ext_price DESC, + i_brand_id +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query56.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query56.sql new file mode 100644 index 000000000..7d2ad0629 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query56.sql @@ -0,0 +1,68 @@ +-- start query 56 in stream 0 using template query56.tpl +WITH ss + AS (SELECT i_item_id, + Sum(ss_ext_sales_price) total_sales + FROM store_sales, + date_dim, + customer_address, + item + WHERE i_item_id IN (SELECT i_item_id + FROM item + WHERE i_color IN ( 'firebrick', 'rosy', 'white' ) + ) + AND ss_item_sk = i_item_sk + AND ss_sold_date_sk = d_date_sk + AND d_year = 1998 + AND d_moy = 3 + AND ss_addr_sk = ca_address_sk + AND ca_gmt_offset = -6 + GROUP BY i_item_id), + cs + AS (SELECT i_item_id, + Sum(cs_ext_sales_price) total_sales + FROM catalog_sales, + date_dim, + customer_address, + item + WHERE i_item_id IN (SELECT i_item_id + FROM item + WHERE i_color IN ( 'firebrick', 'rosy', 'white' ) + ) + AND cs_item_sk = i_item_sk + AND cs_sold_date_sk = d_date_sk + AND d_year = 1998 + AND d_moy = 3 + AND cs_bill_addr_sk = ca_address_sk + AND ca_gmt_offset = -6 + GROUP BY i_item_id), + ws + AS (SELECT i_item_id, + Sum(ws_ext_sales_price) total_sales + FROM web_sales, + date_dim, + customer_address, + item + WHERE i_item_id IN (SELECT i_item_id + FROM item + WHERE i_color IN ( 'firebrick', 'rosy', 'white' ) + ) + AND ws_item_sk = i_item_sk + AND ws_sold_date_sk = d_date_sk + AND d_year = 1998 + AND d_moy = 3 + AND ws_bill_addr_sk = ca_address_sk + AND ca_gmt_offset = -6 + GROUP BY i_item_id) +SELECT i_item_id, + Sum(total_sales) total_sales +FROM (SELECT * + FROM ss + UNION ALL + SELECT * + FROM cs + UNION ALL + SELECT * + FROM ws) tmp1 +GROUP BY i_item_id +ORDER BY total_sales +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query57.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query57.sql new file mode 100644 index 000000000..5cb73bce4 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query57.sql @@ -0,0 +1,66 @@ +-- start query 57 in stream 0 using template query57.tpl +WITH v1 + AS (SELECT i_category, + i_brand, + cc_name, + d_year, + d_moy, + Sum(cs_sales_price) sum_sales + , + Avg(Sum(cs_sales_price)) + OVER ( + partition BY i_category, i_brand, cc_name, d_year) + avg_monthly_sales + , + Rank() + OVER ( + partition BY i_category, i_brand, cc_name + ORDER BY d_year, d_moy) rn + FROM item, + catalog_sales, + date_dim, + call_center + WHERE cs_item_sk = i_item_sk + AND cs_sold_date_sk = d_date_sk + AND cc_call_center_sk = cs_call_center_sk + AND ( d_year = 2000 + OR ( d_year = 2000 - 1 + AND d_moy = 12 ) + OR ( d_year = 2000 + 1 + AND d_moy = 1 ) ) + GROUP BY i_category, + i_brand, + cc_name, + d_year, + d_moy), + v2 + AS (SELECT v1.i_brand, + v1.d_year, + v1.avg_monthly_sales, + v1.sum_sales, + v1_lag.sum_sales psum, + v1_lead.sum_sales nsum + FROM v1, + v1 v1_lag, + v1 v1_lead + WHERE v1.i_category = v1_lag.i_category + AND v1.i_category = v1_lead.i_category + AND v1.i_brand = v1_lag.i_brand + AND v1.i_brand = v1_lead.i_brand + AND v1. cc_name = v1_lag. cc_name + AND v1. cc_name = v1_lead. cc_name + AND v1.rn = v1_lag.rn + 1 + AND v1.rn = v1_lead.rn - 1) +SELECT * +FROM v2 +WHERE d_year = 2000 + AND avg_monthly_sales > 0 + AND CASE + WHEN avg_monthly_sales > 0 THEN Abs(sum_sales - avg_monthly_sales) + / + avg_monthly_sales + ELSE NULL + END > 0.1 +ORDER BY sum_sales - avg_monthly_sales, + 3 +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query58.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query58.sql new file mode 100644 index 000000000..8611390de --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query58.sql @@ -0,0 +1,72 @@ +-- start query 58 in stream 0 using template query58.tpl +WITH ss_items + AS (SELECT i_item_id item_id, + Sum(ss_ext_sales_price) ss_item_rev + FROM store_sales, + item, + date_dim + WHERE ss_item_sk = i_item_sk + AND d_date IN (SELECT d_date + FROM date_dim + WHERE d_week_seq = (SELECT d_week_seq + FROM date_dim + WHERE d_date = '2002-02-25' + )) + AND ss_sold_date_sk = d_date_sk + GROUP BY i_item_id), + cs_items + AS (SELECT i_item_id item_id, + Sum(cs_ext_sales_price) cs_item_rev + FROM catalog_sales, + item, + date_dim + WHERE cs_item_sk = i_item_sk + AND d_date IN (SELECT d_date + FROM date_dim + WHERE d_week_seq = (SELECT d_week_seq + FROM date_dim + WHERE d_date = '2002-02-25' + )) + AND cs_sold_date_sk = d_date_sk + GROUP BY i_item_id), + ws_items + AS (SELECT i_item_id item_id, + Sum(ws_ext_sales_price) ws_item_rev + FROM web_sales, + item, + date_dim + WHERE ws_item_sk = i_item_sk + AND d_date IN (SELECT d_date + FROM date_dim + WHERE d_week_seq = (SELECT d_week_seq + FROM date_dim + WHERE d_date = '2002-02-25' + )) + AND ws_sold_date_sk = d_date_sk + GROUP BY i_item_id) +SELECT ss_items.item_id, + ss_item_rev, + ss_item_rev / ( ss_item_rev + cs_item_rev + ws_item_rev ) / 3 * + 100 ss_dev, + cs_item_rev, + cs_item_rev / ( ss_item_rev + cs_item_rev + ws_item_rev ) / 3 * + 100 cs_dev, + ws_item_rev, + ws_item_rev / ( ss_item_rev + cs_item_rev + ws_item_rev ) / 3 * + 100 ws_dev, + ( ss_item_rev + cs_item_rev + ws_item_rev ) / 3 + average +FROM ss_items, + cs_items, + ws_items +WHERE ss_items.item_id = cs_items.item_id + AND ss_items.item_id = ws_items.item_id + AND ss_item_rev BETWEEN 0.9 * cs_item_rev AND 1.1 * cs_item_rev + AND ss_item_rev BETWEEN 0.9 * ws_item_rev AND 1.1 * ws_item_rev + AND cs_item_rev BETWEEN 0.9 * ss_item_rev AND 1.1 * ss_item_rev + AND cs_item_rev BETWEEN 0.9 * ws_item_rev AND 1.1 * ws_item_rev + AND ws_item_rev BETWEEN 0.9 * ss_item_rev AND 1.1 * ss_item_rev + AND ws_item_rev BETWEEN 0.9 * cs_item_rev AND 1.1 * cs_item_rev +ORDER BY item_id, + ss_item_rev +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query59.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query59.sql new file mode 100644 index 000000000..3caa54048 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query59.sql @@ -0,0 +1,85 @@ +-- start query 59 in stream 0 using template query59.tpl +WITH wss + AS (SELECT d_week_seq, + ss_store_sk, + Sum(CASE + WHEN ( d_day_name = 'Sunday' ) THEN ss_sales_price + ELSE NULL + END) sun_sales, + Sum(CASE + WHEN ( d_day_name = 'Monday' ) THEN ss_sales_price + ELSE NULL + END) mon_sales, + Sum(CASE + WHEN ( d_day_name = 'Tuesday' ) THEN ss_sales_price + ELSE NULL + END) tue_sales, + Sum(CASE + WHEN ( d_day_name = 'Wednesday' ) THEN ss_sales_price + ELSE NULL + END) wed_sales, + Sum(CASE + WHEN ( d_day_name = 'Thursday' ) THEN ss_sales_price + ELSE NULL + END) thu_sales, + Sum(CASE + WHEN ( d_day_name = 'Friday' ) THEN ss_sales_price + ELSE NULL + END) fri_sales, + Sum(CASE + WHEN ( d_day_name = 'Saturday' ) THEN ss_sales_price + ELSE NULL + END) sat_sales + FROM store_sales, + date_dim + WHERE d_date_sk = ss_sold_date_sk + GROUP BY d_week_seq, + ss_store_sk) +SELECT s_store_name1, + s_store_id1, + d_week_seq1, + sun_sales1 / sun_sales2, + mon_sales1 / mon_sales2, + tue_sales1 / tue_sales2, + wed_sales1 / wed_sales2, + thu_sales1 / thu_sales2, + fri_sales1 / fri_sales2, + sat_sales1 / sat_sales2 +FROM (SELECT s_store_name s_store_name1, + wss.d_week_seq d_week_seq1, + s_store_id s_store_id1, + sun_sales sun_sales1, + mon_sales mon_sales1, + tue_sales tue_sales1, + wed_sales wed_sales1, + thu_sales thu_sales1, + fri_sales fri_sales1, + sat_sales sat_sales1 + FROM wss, + store, + date_dim d + WHERE d.d_week_seq = wss.d_week_seq + AND ss_store_sk = s_store_sk + AND d_month_seq BETWEEN 1196 AND 1196 + 11) y, + (SELECT s_store_name s_store_name2, + wss.d_week_seq d_week_seq2, + s_store_id s_store_id2, + sun_sales sun_sales2, + mon_sales mon_sales2, + tue_sales tue_sales2, + wed_sales wed_sales2, + thu_sales thu_sales2, + fri_sales fri_sales2, + sat_sales sat_sales2 + FROM wss, + store, + date_dim d + WHERE d.d_week_seq = wss.d_week_seq + AND ss_store_sk = s_store_sk + AND d_month_seq BETWEEN 1196 + 12 AND 1196 + 23) x +WHERE s_store_id1 = s_store_id2 + AND d_week_seq1 = d_week_seq2 - 52 +ORDER BY s_store_name1, + s_store_id1, + d_week_seq1 +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query6.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query6.sql new file mode 100644 index 000000000..1739ea575 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query6.sql @@ -0,0 +1,23 @@ +-- start query 6 in stream 0 using template query6.tpl +SELECT a.ca_state state, + Count(*) cnt +FROM customer_address a, + customer c, + store_sales s, + date_dim d, + item i +WHERE a.ca_address_sk = c.c_current_addr_sk + AND c.c_customer_sk = s.ss_customer_sk + AND s.ss_sold_date_sk = d.d_date_sk + AND s.ss_item_sk = i.i_item_sk + AND d.d_month_seq = (SELECT DISTINCT ( d_month_seq ) + FROM date_dim + WHERE d_year = 1998 + AND d_moy = 7) + AND i.i_current_price > 1.2 * (SELECT Avg(j.i_current_price) + FROM item j + WHERE j.i_category = i.i_category) +GROUP BY a.ca_state +HAVING Count(*) >= 10 +ORDER BY cnt +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query60.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query60.sql new file mode 100644 index 000000000..541108dea --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query60.sql @@ -0,0 +1,66 @@ +-- start query 60 in stream 0 using template query60.tpl +WITH ss + AS (SELECT i_item_id, + Sum(ss_ext_sales_price) total_sales + FROM store_sales, + date_dim, + customer_address, + item + WHERE i_item_id IN (SELECT i_item_id + FROM item + WHERE i_category IN ( 'Jewelry' )) + AND ss_item_sk = i_item_sk + AND ss_sold_date_sk = d_date_sk + AND d_year = 1999 + AND d_moy = 8 + AND ss_addr_sk = ca_address_sk + AND ca_gmt_offset = -6 + GROUP BY i_item_id), + cs + AS (SELECT i_item_id, + Sum(cs_ext_sales_price) total_sales + FROM catalog_sales, + date_dim, + customer_address, + item + WHERE i_item_id IN (SELECT i_item_id + FROM item + WHERE i_category IN ( 'Jewelry' )) + AND cs_item_sk = i_item_sk + AND cs_sold_date_sk = d_date_sk + AND d_year = 1999 + AND d_moy = 8 + AND cs_bill_addr_sk = ca_address_sk + AND ca_gmt_offset = -6 + GROUP BY i_item_id), + ws + AS (SELECT i_item_id, + Sum(ws_ext_sales_price) total_sales + FROM web_sales, + date_dim, + customer_address, + item + WHERE i_item_id IN (SELECT i_item_id + FROM item + WHERE i_category IN ( 'Jewelry' )) + AND ws_item_sk = i_item_sk + AND ws_sold_date_sk = d_date_sk + AND d_year = 1999 + AND d_moy = 8 + AND ws_bill_addr_sk = ca_address_sk + AND ca_gmt_offset = -6 + GROUP BY i_item_id) +SELECT i_item_id, + Sum(total_sales) total_sales +FROM (SELECT * + FROM ss + UNION ALL + SELECT * + FROM cs + UNION ALL + SELECT * + FROM ws) tmp1 +GROUP BY i_item_id +ORDER BY i_item_id, + total_sales +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query61.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query61.sql new file mode 100644 index 000000000..d6d50753c --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query61.sql @@ -0,0 +1,47 @@ +-- start query 61 in stream 0 using template query61.tpl +SELECT promotions, + total, + Cast(promotions AS DECIMAL(15, 4)) / + Cast(total AS DECIMAL(15, 4)) * 100 +FROM (SELECT Sum(ss_ext_sales_price) promotions + FROM store_sales, + store, + promotion, + date_dim, + customer, + customer_address, + item + WHERE ss_sold_date_sk = d_date_sk + AND ss_store_sk = s_store_sk + AND ss_promo_sk = p_promo_sk + AND ss_customer_sk = c_customer_sk + AND ca_address_sk = c_current_addr_sk + AND ss_item_sk = i_item_sk + AND ca_gmt_offset = -7 + AND i_category = 'Books' + AND ( p_channel_dmail = 'Y' + OR p_channel_email = 'Y' + OR p_channel_tv = 'Y' ) + AND s_gmt_offset = -7 + AND d_year = 2001 + AND d_moy = 12) promotional_sales, + (SELECT Sum(ss_ext_sales_price) total + FROM store_sales, + store, + date_dim, + customer, + customer_address, + item + WHERE ss_sold_date_sk = d_date_sk + AND ss_store_sk = s_store_sk + AND ss_customer_sk = c_customer_sk + AND ca_address_sk = c_current_addr_sk + AND ss_item_sk = i_item_sk + AND ca_gmt_offset = -7 + AND i_category = 'Books' + AND s_gmt_offset = -7 + AND d_year = 2001 + AND d_moy = 12) all_sales +ORDER BY promotions, + total +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query62.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query62.sql new file mode 100644 index 000000000..54df88ed2 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query62.sql @@ -0,0 +1,45 @@ +-- start query 62 in stream 0 using template query62.tpl +SELECT Substr(w_warehouse_name, 1, 20), + sm_type, + web_name, + Sum(CASE + WHEN ( ws_ship_date_sk - ws_sold_date_sk <= 30 ) THEN 1 + ELSE 0 + END) AS "30 days", + Sum(CASE + WHEN ( ws_ship_date_sk - ws_sold_date_sk > 30 ) + AND ( ws_ship_date_sk - ws_sold_date_sk <= 60 ) THEN 1 + ELSE 0 + END) AS "31-60 days", + Sum(CASE + WHEN ( ws_ship_date_sk - ws_sold_date_sk > 60 ) + AND ( ws_ship_date_sk - ws_sold_date_sk <= 90 ) THEN 1 + ELSE 0 + END) AS "61-90 days", + Sum(CASE + WHEN ( ws_ship_date_sk - ws_sold_date_sk > 90 ) + AND ( ws_ship_date_sk - ws_sold_date_sk <= 120 ) THEN + 1 + ELSE 0 + END) AS "91-120 days", + Sum(CASE + WHEN ( ws_ship_date_sk - ws_sold_date_sk > 120 ) THEN 1 + ELSE 0 + END) AS ">120 days" +FROM web_sales, + warehouse, + ship_mode, + web_site, + date_dim +WHERE d_month_seq BETWEEN 1222 AND 1222 + 11 + AND ws_ship_date_sk = d_date_sk + AND ws_warehouse_sk = w_warehouse_sk + AND ws_ship_mode_sk = sm_ship_mode_sk + AND ws_web_site_sk = web_site_sk +GROUP BY Substr(w_warehouse_name, 1, 20), + sm_type, + web_name +ORDER BY Substr(w_warehouse_name, 1, 20), + sm_type, + web_name +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query63.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query63.sql new file mode 100644 index 000000000..8706bb492 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query63.sql @@ -0,0 +1,45 @@ +-- start query 63 in stream 0 using template query63.tpl +SELECT * +FROM (SELECT i_manager_id, + Sum(ss_sales_price) sum_sales, + Avg(Sum(ss_sales_price)) + OVER ( + partition BY i_manager_id) avg_monthly_sales + FROM item, + store_sales, + date_dim, + store + WHERE ss_item_sk = i_item_sk + AND ss_sold_date_sk = d_date_sk + AND ss_store_sk = s_store_sk + AND d_month_seq IN ( 1200, 1200 + 1, 1200 + 2, 1200 + 3, + 1200 + 4, 1200 + 5, 1200 + 6, 1200 + 7, + 1200 + 8, 1200 + 9, 1200 + 10, 1200 + 11 ) + AND ( ( i_category IN ( 'Books', 'Children', 'Electronics' ) + AND i_class IN ( 'personal', 'portable', 'reference', + 'self-help' ) + AND i_brand IN ( 'scholaramalgamalg #14', + 'scholaramalgamalg #7' + , + 'exportiunivamalg #9', + 'scholaramalgamalg #9' ) + ) + OR ( i_category IN ( 'Women', 'Music', 'Men' ) + AND i_class IN ( 'accessories', 'classical', + 'fragrances', + 'pants' ) + AND i_brand IN ( 'amalgimporto #1', + 'edu packscholar #1', + 'exportiimporto #1', + 'importoamalg #1' ) ) ) + GROUP BY i_manager_id, + d_moy) tmp1 +WHERE CASE + WHEN avg_monthly_sales > 0 THEN Abs (sum_sales - avg_monthly_sales) / + avg_monthly_sales + ELSE NULL + END > 0.1 +ORDER BY i_manager_id, + avg_monthly_sales, + sum_sales +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query64.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query64.sql new file mode 100644 index 000000000..0c5d1d3d8 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query64.sql @@ -0,0 +1,122 @@ +-- start query 64 in stream 0 using template query64.tpl +WITH cs_ui + AS (SELECT cs_item_sk, + Sum(cs_ext_list_price) AS sale, + Sum(cr_refunded_cash + cr_reversed_charge + + cr_store_credit) AS refund + FROM catalog_sales, + catalog_returns + WHERE cs_item_sk = cr_item_sk + AND cs_order_number = cr_order_number + GROUP BY cs_item_sk + HAVING Sum(cs_ext_list_price) > 2 * Sum( + cr_refunded_cash + cr_reversed_charge + + cr_store_credit)), + cross_sales + AS (SELECT i_product_name product_name, + i_item_sk item_sk, + s_store_name store_name, + s_zip store_zip, + ad1.ca_street_number b_street_number, + ad1.ca_street_name b_streen_name, + ad1.ca_city b_city, + ad1.ca_zip b_zip, + ad2.ca_street_number c_street_number, + ad2.ca_street_name c_street_name, + ad2.ca_city c_city, + ad2.ca_zip c_zip, + d1.d_year AS syear, + d2.d_year AS fsyear, + d3.d_year s2year, + Count(*) cnt, + Sum(ss_wholesale_cost) s1, + Sum(ss_list_price) s2, + Sum(ss_coupon_amt) s3 + FROM store_sales, + store_returns, + cs_ui, + date_dim d1, + date_dim d2, + date_dim d3, + store, + customer, + customer_demographics cd1, + customer_demographics cd2, + promotion, + household_demographics hd1, + household_demographics hd2, + customer_address ad1, + customer_address ad2, + income_band ib1, + income_band ib2, + item + WHERE ss_store_sk = s_store_sk + AND ss_sold_date_sk = d1.d_date_sk + AND ss_customer_sk = c_customer_sk + AND ss_cdemo_sk = cd1.cd_demo_sk + AND ss_hdemo_sk = hd1.hd_demo_sk + AND ss_addr_sk = ad1.ca_address_sk + AND ss_item_sk = i_item_sk + AND ss_item_sk = sr_item_sk + AND ss_ticket_number = sr_ticket_number + AND ss_item_sk = cs_ui.cs_item_sk + AND c_current_cdemo_sk = cd2.cd_demo_sk + AND c_current_hdemo_sk = hd2.hd_demo_sk + AND c_current_addr_sk = ad2.ca_address_sk + AND c_first_sales_date_sk = d2.d_date_sk + AND c_first_shipto_date_sk = d3.d_date_sk + AND ss_promo_sk = p_promo_sk + AND hd1.hd_income_band_sk = ib1.ib_income_band_sk + AND hd2.hd_income_band_sk = ib2.ib_income_band_sk + AND cd1.cd_marital_status <> cd2.cd_marital_status + AND i_color IN ( 'cyan', 'peach', 'blush', 'frosted', + 'powder', 'orange' ) + AND i_current_price BETWEEN 58 AND 58 + 10 + AND i_current_price BETWEEN 58 + 1 AND 58 + 15 + GROUP BY i_product_name, + i_item_sk, + s_store_name, + s_zip, + ad1.ca_street_number, + ad1.ca_street_name, + ad1.ca_city, + ad1.ca_zip, + ad2.ca_street_number, + ad2.ca_street_name, + ad2.ca_city, + ad2.ca_zip, + d1.d_year, + d2.d_year, + d3.d_year) +SELECT cs1.product_name, + cs1.store_name, + cs1.store_zip, + cs1.b_street_number, + cs1.b_streen_name, + cs1.b_city, + cs1.b_zip, + cs1.c_street_number, + cs1.c_street_name, + cs1.c_city, + cs1.c_zip, + cs1.syear, + cs1.cnt, + cs1.s1, + cs1.s2, + cs1.s3, + cs2.s1, + cs2.s2, + cs2.s3, + cs2.syear, + cs2.cnt +FROM cross_sales cs1, + cross_sales cs2 +WHERE cs1.item_sk = cs2.item_sk + AND cs1.syear = 2001 + AND cs2.syear = 2001 + 1 + AND cs2.cnt <= cs1.cnt + AND cs1.store_name = cs2.store_name + AND cs1.store_zip = cs2.store_zip +ORDER BY cs1.product_name, + cs1.store_name, + cs2.cnt; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query65.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query65.sql new file mode 100644 index 000000000..399f128c3 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query65.sql @@ -0,0 +1,37 @@ +-- start query 65 in stream 0 using template query65.tpl +SELECT s_store_name, + i_item_desc, + sc.revenue, + i_current_price, + i_wholesale_cost, + i_brand +FROM store, + item, + (SELECT ss_store_sk, + Avg(revenue) AS ave + FROM (SELECT ss_store_sk, + ss_item_sk, + Sum(ss_sales_price) AS revenue + FROM store_sales, + date_dim + WHERE ss_sold_date_sk = d_date_sk + AND d_month_seq BETWEEN 1199 AND 1199 + 11 + GROUP BY ss_store_sk, + ss_item_sk) sa + GROUP BY ss_store_sk) sb, + (SELECT ss_store_sk, + ss_item_sk, + Sum(ss_sales_price) AS revenue + FROM store_sales, + date_dim + WHERE ss_sold_date_sk = d_date_sk + AND d_month_seq BETWEEN 1199 AND 1199 + 11 + GROUP BY ss_store_sk, + ss_item_sk) sc +WHERE sb.ss_store_sk = sc.ss_store_sk + AND sc.revenue <= 0.1 * sb.ave + AND s_store_sk = sc.ss_store_sk + AND i_item_sk = sc.ss_item_sk +ORDER BY s_store_name, + i_item_desc +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query66.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query66.sql new file mode 100644 index 000000000..79446555f --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query66.sql @@ -0,0 +1,306 @@ +-- start query 66 in stream 0 using template query66.tpl +SELECT w_warehouse_name, + w_warehouse_sq_ft, + w_city, + w_county, + w_state, + w_country, + ship_carriers, + year1, + Sum(jan_sales) AS jan_sales, + Sum(feb_sales) AS feb_sales, + Sum(mar_sales) AS mar_sales, + Sum(apr_sales) AS apr_sales, + Sum(may_sales) AS may_sales, + Sum(jun_sales) AS jun_sales, + Sum(jul_sales) AS jul_sales, + Sum(aug_sales) AS aug_sales, + Sum(sep_sales) AS sep_sales, + Sum(oct_sales) AS oct_sales, + Sum(nov_sales) AS nov_sales, + Sum(dec_sales) AS dec_sales, + Sum(jan_sales / w_warehouse_sq_ft) AS jan_sales_per_sq_foot, + Sum(feb_sales / w_warehouse_sq_ft) AS feb_sales_per_sq_foot, + Sum(mar_sales / w_warehouse_sq_ft) AS mar_sales_per_sq_foot, + Sum(apr_sales / w_warehouse_sq_ft) AS apr_sales_per_sq_foot, + Sum(may_sales / w_warehouse_sq_ft) AS may_sales_per_sq_foot, + Sum(jun_sales / w_warehouse_sq_ft) AS jun_sales_per_sq_foot, + Sum(jul_sales / w_warehouse_sq_ft) AS jul_sales_per_sq_foot, + Sum(aug_sales / w_warehouse_sq_ft) AS aug_sales_per_sq_foot, + Sum(sep_sales / w_warehouse_sq_ft) AS sep_sales_per_sq_foot, + Sum(oct_sales / w_warehouse_sq_ft) AS oct_sales_per_sq_foot, + Sum(nov_sales / w_warehouse_sq_ft) AS nov_sales_per_sq_foot, + Sum(dec_sales / w_warehouse_sq_ft) AS dec_sales_per_sq_foot, + Sum(jan_net) AS jan_net, + Sum(feb_net) AS feb_net, + Sum(mar_net) AS mar_net, + Sum(apr_net) AS apr_net, + Sum(may_net) AS may_net, + Sum(jun_net) AS jun_net, + Sum(jul_net) AS jul_net, + Sum(aug_net) AS aug_net, + Sum(sep_net) AS sep_net, + Sum(oct_net) AS oct_net, + Sum(nov_net) AS nov_net, + Sum(dec_net) AS dec_net +FROM (SELECT w_warehouse_name, + w_warehouse_sq_ft, + w_city, + w_county, + w_state, + w_country, + 'ZOUROS' + || ',' + || 'ZHOU' AS ship_carriers, + d_year AS year1, + Sum(CASE + WHEN d_moy = 1 THEN ws_ext_sales_price * ws_quantity + ELSE 0 + END) AS jan_sales, + Sum(CASE + WHEN d_moy = 2 THEN ws_ext_sales_price * ws_quantity + ELSE 0 + END) AS feb_sales, + Sum(CASE + WHEN d_moy = 3 THEN ws_ext_sales_price * ws_quantity + ELSE 0 + END) AS mar_sales, + Sum(CASE + WHEN d_moy = 4 THEN ws_ext_sales_price * ws_quantity + ELSE 0 + END) AS apr_sales, + Sum(CASE + WHEN d_moy = 5 THEN ws_ext_sales_price * ws_quantity + ELSE 0 + END) AS may_sales, + Sum(CASE + WHEN d_moy = 6 THEN ws_ext_sales_price * ws_quantity + ELSE 0 + END) AS jun_sales, + Sum(CASE + WHEN d_moy = 7 THEN ws_ext_sales_price * ws_quantity + ELSE 0 + END) AS jul_sales, + Sum(CASE + WHEN d_moy = 8 THEN ws_ext_sales_price * ws_quantity + ELSE 0 + END) AS aug_sales, + Sum(CASE + WHEN d_moy = 9 THEN ws_ext_sales_price * ws_quantity + ELSE 0 + END) AS sep_sales, + Sum(CASE + WHEN d_moy = 10 THEN ws_ext_sales_price * ws_quantity + ELSE 0 + END) AS oct_sales, + Sum(CASE + WHEN d_moy = 11 THEN ws_ext_sales_price * ws_quantity + ELSE 0 + END) AS nov_sales, + Sum(CASE + WHEN d_moy = 12 THEN ws_ext_sales_price * ws_quantity + ELSE 0 + END) AS dec_sales, + Sum(CASE + WHEN d_moy = 1 THEN ws_net_paid_inc_ship * ws_quantity + ELSE 0 + END) AS jan_net, + Sum(CASE + WHEN d_moy = 2 THEN ws_net_paid_inc_ship * ws_quantity + ELSE 0 + END) AS feb_net, + Sum(CASE + WHEN d_moy = 3 THEN ws_net_paid_inc_ship * ws_quantity + ELSE 0 + END) AS mar_net, + Sum(CASE + WHEN d_moy = 4 THEN ws_net_paid_inc_ship * ws_quantity + ELSE 0 + END) AS apr_net, + Sum(CASE + WHEN d_moy = 5 THEN ws_net_paid_inc_ship * ws_quantity + ELSE 0 + END) AS may_net, + Sum(CASE + WHEN d_moy = 6 THEN ws_net_paid_inc_ship * ws_quantity + ELSE 0 + END) AS jun_net, + Sum(CASE + WHEN d_moy = 7 THEN ws_net_paid_inc_ship * ws_quantity + ELSE 0 + END) AS jul_net, + Sum(CASE + WHEN d_moy = 8 THEN ws_net_paid_inc_ship * ws_quantity + ELSE 0 + END) AS aug_net, + Sum(CASE + WHEN d_moy = 9 THEN ws_net_paid_inc_ship * ws_quantity + ELSE 0 + END) AS sep_net, + Sum(CASE + WHEN d_moy = 10 THEN ws_net_paid_inc_ship * ws_quantity + ELSE 0 + END) AS oct_net, + Sum(CASE + WHEN d_moy = 11 THEN ws_net_paid_inc_ship * ws_quantity + ELSE 0 + END) AS nov_net, + Sum(CASE + WHEN d_moy = 12 THEN ws_net_paid_inc_ship * ws_quantity + ELSE 0 + END) AS dec_net + FROM web_sales, + warehouse, + date_dim, + time_dim, + ship_mode + WHERE ws_warehouse_sk = w_warehouse_sk + AND ws_sold_date_sk = d_date_sk + AND ws_sold_time_sk = t_time_sk + AND ws_ship_mode_sk = sm_ship_mode_sk + AND d_year = 1998 + AND t_time BETWEEN 7249 AND 7249 + 28800 + AND sm_carrier IN ( 'ZOUROS', 'ZHOU' ) + GROUP BY w_warehouse_name, + w_warehouse_sq_ft, + w_city, + w_county, + w_state, + w_country, + d_year + UNION ALL + SELECT w_warehouse_name, + w_warehouse_sq_ft, + w_city, + w_county, + w_state, + w_country, + 'ZOUROS' + || ',' + || 'ZHOU' AS ship_carriers, + d_year AS year1, + Sum(CASE + WHEN d_moy = 1 THEN cs_ext_sales_price * cs_quantity + ELSE 0 + END) AS jan_sales, + Sum(CASE + WHEN d_moy = 2 THEN cs_ext_sales_price * cs_quantity + ELSE 0 + END) AS feb_sales, + Sum(CASE + WHEN d_moy = 3 THEN cs_ext_sales_price * cs_quantity + ELSE 0 + END) AS mar_sales, + Sum(CASE + WHEN d_moy = 4 THEN cs_ext_sales_price * cs_quantity + ELSE 0 + END) AS apr_sales, + Sum(CASE + WHEN d_moy = 5 THEN cs_ext_sales_price * cs_quantity + ELSE 0 + END) AS may_sales, + Sum(CASE + WHEN d_moy = 6 THEN cs_ext_sales_price * cs_quantity + ELSE 0 + END) AS jun_sales, + Sum(CASE + WHEN d_moy = 7 THEN cs_ext_sales_price * cs_quantity + ELSE 0 + END) AS jul_sales, + Sum(CASE + WHEN d_moy = 8 THEN cs_ext_sales_price * cs_quantity + ELSE 0 + END) AS aug_sales, + Sum(CASE + WHEN d_moy = 9 THEN cs_ext_sales_price * cs_quantity + ELSE 0 + END) AS sep_sales, + Sum(CASE + WHEN d_moy = 10 THEN cs_ext_sales_price * cs_quantity + ELSE 0 + END) AS oct_sales, + Sum(CASE + WHEN d_moy = 11 THEN cs_ext_sales_price * cs_quantity + ELSE 0 + END) AS nov_sales, + Sum(CASE + WHEN d_moy = 12 THEN cs_ext_sales_price * cs_quantity + ELSE 0 + END) AS dec_sales, + Sum(CASE + WHEN d_moy = 1 THEN cs_net_paid * cs_quantity + ELSE 0 + END) AS jan_net, + Sum(CASE + WHEN d_moy = 2 THEN cs_net_paid * cs_quantity + ELSE 0 + END) AS feb_net, + Sum(CASE + WHEN d_moy = 3 THEN cs_net_paid * cs_quantity + ELSE 0 + END) AS mar_net, + Sum(CASE + WHEN d_moy = 4 THEN cs_net_paid * cs_quantity + ELSE 0 + END) AS apr_net, + Sum(CASE + WHEN d_moy = 5 THEN cs_net_paid * cs_quantity + ELSE 0 + END) AS may_net, + Sum(CASE + WHEN d_moy = 6 THEN cs_net_paid * cs_quantity + ELSE 0 + END) AS jun_net, + Sum(CASE + WHEN d_moy = 7 THEN cs_net_paid * cs_quantity + ELSE 0 + END) AS jul_net, + Sum(CASE + WHEN d_moy = 8 THEN cs_net_paid * cs_quantity + ELSE 0 + END) AS aug_net, + Sum(CASE + WHEN d_moy = 9 THEN cs_net_paid * cs_quantity + ELSE 0 + END) AS sep_net, + Sum(CASE + WHEN d_moy = 10 THEN cs_net_paid * cs_quantity + ELSE 0 + END) AS oct_net, + Sum(CASE + WHEN d_moy = 11 THEN cs_net_paid * cs_quantity + ELSE 0 + END) AS nov_net, + Sum(CASE + WHEN d_moy = 12 THEN cs_net_paid * cs_quantity + ELSE 0 + END) AS dec_net + FROM catalog_sales, + warehouse, + date_dim, + time_dim, + ship_mode + WHERE cs_warehouse_sk = w_warehouse_sk + AND cs_sold_date_sk = d_date_sk + AND cs_sold_time_sk = t_time_sk + AND cs_ship_mode_sk = sm_ship_mode_sk + AND d_year = 1998 + AND t_time BETWEEN 7249 AND 7249 + 28800 + AND sm_carrier IN ( 'ZOUROS', 'ZHOU' ) + GROUP BY w_warehouse_name, + w_warehouse_sq_ft, + w_city, + w_county, + w_state, + w_country, + d_year) x +GROUP BY w_warehouse_name, + w_warehouse_sq_ft, + w_city, + w_county, + w_state, + w_country, + ship_carriers, + year1 +ORDER BY w_warehouse_name +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query67.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query67.sql new file mode 100644 index 000000000..18c39edb5 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query67.sql @@ -0,0 +1,43 @@ +-- start query 67 in stream 0 using template query67.tpl +select * +from (select i_category + ,i_class + ,i_brand + ,i_product_name + ,d_year + ,d_qoy + ,d_moy + ,s_store_id + ,sumsales + ,rank() over (partition by i_category order by sumsales desc) rk + from (select i_category + ,i_class + ,i_brand + ,i_product_name + ,d_year + ,d_qoy + ,d_moy + ,s_store_id + ,sum(coalesce(ss_sales_price*ss_quantity,0)) sumsales + from store_sales + ,date_dim + ,store + ,item + where ss_sold_date_sk=d_date_sk + and ss_item_sk=i_item_sk + and ss_store_sk = s_store_sk + and d_month_seq between 1181 and 1181+11 + group by rollup(i_category, i_class, i_brand, i_product_name, d_year, d_qoy, d_moy,s_store_id))dw1) dw2 +where rk <= 100 +order by i_category + ,i_class + ,i_brand + ,i_product_name + ,d_year + ,d_qoy + ,d_moy + ,s_store_id + ,sumsales + ,rk +limit 100 +; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query68.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query68.sql new file mode 100644 index 000000000..f943603f8 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query68.sql @@ -0,0 +1,41 @@ +-- start query 68 in stream 0 using template query68.tpl +SELECT c_last_name, + c_first_name, + ca_city, + bought_city, + ss_ticket_number, + extended_price, + extended_tax, + list_price +FROM (SELECT ss_ticket_number, + ss_customer_sk, + ca_city bought_city, + Sum(ss_ext_sales_price) extended_price, + Sum(ss_ext_list_price) list_price, + Sum(ss_ext_tax) extended_tax + FROM store_sales, + date_dim, + store, + household_demographics, + customer_address + WHERE store_sales.ss_sold_date_sk = date_dim.d_date_sk + AND store_sales.ss_store_sk = store.s_store_sk + AND store_sales.ss_hdemo_sk = household_demographics.hd_demo_sk + AND store_sales.ss_addr_sk = customer_address.ca_address_sk + AND date_dim.d_dom BETWEEN 1 AND 2 + AND ( household_demographics.hd_dep_count = 8 + OR household_demographics.hd_vehicle_count = 3 ) + AND date_dim.d_year IN ( 1998, 1998 + 1, 1998 + 2 ) + AND store.s_city IN ( 'Fairview', 'Midway' ) + GROUP BY ss_ticket_number, + ss_customer_sk, + ss_addr_sk, + ca_city) dn, + customer, + customer_address current_addr +WHERE ss_customer_sk = c_customer_sk + AND customer.c_current_addr_sk = current_addr.ca_address_sk + AND current_addr.ca_city <> bought_city +ORDER BY c_last_name, + ss_ticket_number +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query69.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query69.sql new file mode 100644 index 000000000..e22094bd8 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query69.sql @@ -0,0 +1,46 @@ +SELECT cd_gender, + cd_marital_status, + cd_education_status, + Count(*) cnt1, + cd_purchase_estimate, + Count(*) cnt2, + cd_credit_rating, + Count(*) cnt3 +FROM customer c, + customer_address ca, + customer_demographics +WHERE c.c_current_addr_sk = ca.ca_address_sk + AND ca_state IN ( 'KS', 'AZ', 'NE' ) + AND cd_demo_sk = c.c_current_cdemo_sk + AND EXISTS (SELECT * + FROM store_sales, + date_dim + WHERE c.c_customer_sk = ss_customer_sk + AND ss_sold_date_sk = d_date_sk + AND d_year = 2004 + AND d_moy BETWEEN 3 AND 3 + 2) + AND ( NOT EXISTS (SELECT * + FROM web_sales, + date_dim + WHERE c.c_customer_sk = ws_bill_customer_sk + AND ws_sold_date_sk = d_date_sk + AND d_year = 2004 + AND d_moy BETWEEN 3 AND 3 + 2) + AND NOT EXISTS (SELECT * + FROM catalog_sales, + date_dim + WHERE c.c_customer_sk = cs_ship_customer_sk + AND cs_sold_date_sk = d_date_sk + AND d_year = 2004 + AND d_moy BETWEEN 3 AND 3 + 2) ) +GROUP BY cd_gender, + cd_marital_status, + cd_education_status, + cd_purchase_estimate, + cd_credit_rating +ORDER BY cd_gender, + cd_marital_status, + cd_education_status, + cd_purchase_estimate, + cd_credit_rating +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query7.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query7.sql new file mode 100644 index 000000000..6e28b46c8 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query7.sql @@ -0,0 +1,24 @@ +-- start query 7 in stream 0 using template query7.tpl +SELECT i_item_id, + Avg(ss_quantity) agg1, + Avg(ss_list_price) agg2, + Avg(ss_coupon_amt) agg3, + Avg(ss_sales_price) agg4 +FROM store_sales, + customer_demographics, + date_dim, + item, + promotion +WHERE ss_sold_date_sk = d_date_sk + AND ss_item_sk = i_item_sk + AND ss_cdemo_sk = cd_demo_sk + AND ss_promo_sk = p_promo_sk + AND cd_gender = 'F' + AND cd_marital_status = 'W' + AND cd_education_status = '2 yr Degree' + AND ( p_channel_email = 'N' + OR p_channel_event = 'N' ) + AND d_year = 1998 +GROUP BY i_item_id +ORDER BY i_item_id +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query70.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query70.sql new file mode 100644 index 000000000..34c5e0656 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query70.sql @@ -0,0 +1,40 @@ +-- start query 70 in stream 0 using template query70.tpl +SELECT Sum(ss_net_profit) AS total_sum, + s_state, + s_county, + Grouping(s_state) + Grouping(s_county) AS lochierarchy, + Rank() + OVER ( + partition BY Grouping(s_state)+Grouping(s_county), CASE WHEN + Grouping( + s_county) = 0 THEN s_state END + ORDER BY Sum(ss_net_profit) DESC) AS rank_within_parent +FROM store_sales, + date_dim d1, + store +WHERE d1.d_month_seq BETWEEN 1200 AND 1200 + 11 + AND d1.d_date_sk = ss_sold_date_sk + AND s_store_sk = ss_store_sk + AND s_state IN (SELECT s_state + FROM (SELECT s_state AS + s_state, + Rank() + OVER ( + partition BY s_state + ORDER BY Sum(ss_net_profit) DESC) AS + ranking + FROM store_sales, + store, + date_dim + WHERE d_month_seq BETWEEN 1200 AND 1200 + 11 + AND d_date_sk = ss_sold_date_sk + AND s_store_sk = ss_store_sk + GROUP BY s_state) tmp1 + WHERE ranking <= 5) +GROUP BY rollup( s_state, s_county ) +ORDER BY lochierarchy DESC, + CASE + WHEN lochierarchy = 0 THEN s_state + END, + rank_within_parent +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query71.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query71.sql new file mode 100644 index 000000000..42075471e --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query71.sql @@ -0,0 +1,48 @@ +-- start query 71 in stream 0 using template query71.tpl +SELECT i_brand_id brand_id, + i_brand brand, + t_hour, + t_minute, + Sum(ext_price) ext_price +FROM item, + (SELECT ws_ext_sales_price AS ext_price, + ws_sold_date_sk AS sold_date_sk, + ws_item_sk AS sold_item_sk, + ws_sold_time_sk AS time_sk + FROM web_sales, + date_dim + WHERE d_date_sk = ws_sold_date_sk + AND d_moy = 11 + AND d_year = 2001 + UNION ALL + SELECT cs_ext_sales_price AS ext_price, + cs_sold_date_sk AS sold_date_sk, + cs_item_sk AS sold_item_sk, + cs_sold_time_sk AS time_sk + FROM catalog_sales, + date_dim + WHERE d_date_sk = cs_sold_date_sk + AND d_moy = 11 + AND d_year = 2001 + UNION ALL + SELECT ss_ext_sales_price AS ext_price, + ss_sold_date_sk AS sold_date_sk, + ss_item_sk AS sold_item_sk, + ss_sold_time_sk AS time_sk + FROM store_sales, + date_dim + WHERE d_date_sk = ss_sold_date_sk + AND d_moy = 11 + AND d_year = 2001) AS tmp, + time_dim +WHERE sold_item_sk = i_item_sk + AND i_manager_id = 1 + AND time_sk = t_time_sk + AND ( t_meal_time = 'breakfast' + OR t_meal_time = 'dinner' ) +GROUP BY i_brand, + i_brand_id, + t_hour, + t_minute +ORDER BY ext_price DESC, + i_brand_id; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query72.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query72.sql new file mode 100644 index 000000000..e0e082cb4 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query72.sql @@ -0,0 +1,49 @@ +-- start query 72 in stream 0 using template query72.tpl +SELECT i_item_desc, + w_warehouse_name, + d1.d_week_seq, + Sum(CASE + WHEN p_promo_sk IS NULL THEN 1 + ELSE 0 + END) no_promo, + Sum(CASE + WHEN p_promo_sk IS NOT NULL THEN 1 + ELSE 0 + END) promo, + Count(*) total_cnt +FROM catalog_sales + JOIN inventory + ON ( cs_item_sk = inv_item_sk ) + JOIN warehouse + ON ( w_warehouse_sk = inv_warehouse_sk ) + JOIN item + ON ( i_item_sk = cs_item_sk ) + JOIN customer_demographics + ON ( cs_bill_cdemo_sk = cd_demo_sk ) + JOIN household_demographics + ON ( cs_bill_hdemo_sk = hd_demo_sk ) + JOIN date_dim d1 + ON ( cs_sold_date_sk = d1.d_date_sk ) + JOIN date_dim d2 + ON ( inv_date_sk = d2.d_date_sk ) + JOIN date_dim d3 + ON ( cs_ship_date_sk = d3.d_date_sk ) + LEFT OUTER JOIN promotion + ON ( cs_promo_sk = p_promo_sk ) + LEFT OUTER JOIN catalog_returns + ON ( cr_item_sk = cs_item_sk + AND cr_order_number = cs_order_number ) +WHERE d1.d_week_seq = d2.d_week_seq + AND inv_quantity_on_hand < cs_quantity + AND d3.d_date > d1.d_date + INTERVAL '5' day + AND hd_buy_potential = '501-1000' + AND d1.d_year = 2002 + AND cd_marital_status = 'M' +GROUP BY i_item_desc, + w_warehouse_name, + d1.d_week_seq +ORDER BY total_cnt DESC, + i_item_desc, + w_warehouse_name, + d_week_seq +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query73.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query73.sql new file mode 100644 index 000000000..d56d682c3 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query73.sql @@ -0,0 +1,39 @@ +-- start query 73 in stream 0 using template query73.tpl +SELECT c_last_name, + c_first_name, + c_salutation, + c_preferred_cust_flag, + ss_ticket_number, + cnt +FROM (SELECT ss_ticket_number, + ss_customer_sk, + Count(*) cnt + FROM store_sales, + date_dim, + store, + household_demographics + WHERE store_sales.ss_sold_date_sk = date_dim.d_date_sk + AND store_sales.ss_store_sk = store.s_store_sk + AND store_sales.ss_hdemo_sk = household_demographics.hd_demo_sk + AND date_dim.d_dom BETWEEN 1 AND 2 + AND ( household_demographics.hd_buy_potential = '>10000' + OR household_demographics.hd_buy_potential = '0-500' ) + AND household_demographics.hd_vehicle_count > 0 + AND CASE + WHEN household_demographics.hd_vehicle_count > 0 THEN + household_demographics.hd_dep_count / + household_demographics.hd_vehicle_count + ELSE NULL + END > 1 + AND date_dim.d_year IN ( 2000, 2000 + 1, 2000 + 2 ) + AND store.s_county IN ( 'Williamson County', 'Williamson County', + 'Williamson County', + 'Williamson County' + ) + GROUP BY ss_ticket_number, + ss_customer_sk) dj, + customer +WHERE ss_customer_sk = c_customer_sk + AND cnt BETWEEN 1 AND 5 +ORDER BY cnt DESC, + c_last_name ASC; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query74.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query74.sql new file mode 100644 index 000000000..f8d781f79 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query74.sql @@ -0,0 +1,69 @@ +-- start query 74 in stream 0 using template query74.tpl +WITH year_total + AS (SELECT c_customer_id customer_id, + c_first_name customer_first_name, + c_last_name customer_last_name, + d_year AS year1, + Sum(ss_net_paid) year_total, + 's' sale_type + FROM customer, + store_sales, + date_dim + WHERE c_customer_sk = ss_customer_sk + AND ss_sold_date_sk = d_date_sk + AND d_year IN ( 1999, 1999 + 1 ) + GROUP BY c_customer_id, + c_first_name, + c_last_name, + d_year + UNION ALL + SELECT c_customer_id customer_id, + c_first_name customer_first_name, + c_last_name customer_last_name, + d_year AS year1, + Sum(ws_net_paid) year_total, + 'w' sale_type + FROM customer, + web_sales, + date_dim + WHERE c_customer_sk = ws_bill_customer_sk + AND ws_sold_date_sk = d_date_sk + AND d_year IN ( 1999, 1999 + 1 ) + GROUP BY c_customer_id, + c_first_name, + c_last_name, + d_year) +SELECT t_s_secyear.customer_id, + t_s_secyear.customer_first_name, + t_s_secyear.customer_last_name +FROM year_total t_s_firstyear, + year_total t_s_secyear, + year_total t_w_firstyear, + year_total t_w_secyear +WHERE t_s_secyear.customer_id = t_s_firstyear.customer_id + AND t_s_firstyear.customer_id = t_w_secyear.customer_id + AND t_s_firstyear.customer_id = t_w_firstyear.customer_id + AND t_s_firstyear.sale_type = 's' + AND t_w_firstyear.sale_type = 'w' + AND t_s_secyear.sale_type = 's' + AND t_w_secyear.sale_type = 'w' + AND t_s_firstyear.year1 = 1999 + AND t_s_secyear.year1 = 1999 + 1 + AND t_w_firstyear.year1 = 1999 + AND t_w_secyear.year1 = 1999 + 1 + AND t_s_firstyear.year_total > 0 + AND t_w_firstyear.year_total > 0 + AND CASE + WHEN t_w_firstyear.year_total > 0 THEN t_w_secyear.year_total / + t_w_firstyear.year_total + ELSE NULL + END > CASE + WHEN t_s_firstyear.year_total > 0 THEN + t_s_secyear.year_total / + t_s_firstyear.year_total + ELSE NULL + END +ORDER BY 1, + 2, + 3 +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query75.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query75.sql new file mode 100644 index 000000000..25d1ccbd4 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query75.sql @@ -0,0 +1,93 @@ +-- start query 75 in stream 0 using template query75.tpl +WITH all_sales + AS (SELECT d_year, + i_brand_id, + i_class_id, + i_category_id, + i_manufact_id, + Sum(sales_cnt) AS sales_cnt, + Sum(sales_amt) AS sales_amt + FROM (SELECT d_year, + i_brand_id, + i_class_id, + i_category_id, + i_manufact_id, + cs_quantity - COALESCE(cr_return_quantity, 0) AS + sales_cnt, + cs_ext_sales_price - COALESCE(cr_return_amount, 0.0) AS + sales_amt + FROM catalog_sales + JOIN item + ON i_item_sk = cs_item_sk + JOIN date_dim + ON d_date_sk = cs_sold_date_sk + LEFT JOIN catalog_returns + ON ( cs_order_number = cr_order_number + AND cs_item_sk = cr_item_sk ) + WHERE i_category = 'Men' + UNION + SELECT d_year, + i_brand_id, + i_class_id, + i_category_id, + i_manufact_id, + ss_quantity - COALESCE(sr_return_quantity, 0) AS + sales_cnt, + ss_ext_sales_price - COALESCE(sr_return_amt, 0.0) AS + sales_amt + FROM store_sales + JOIN item + ON i_item_sk = ss_item_sk + JOIN date_dim + ON d_date_sk = ss_sold_date_sk + LEFT JOIN store_returns + ON ( ss_ticket_number = sr_ticket_number + AND ss_item_sk = sr_item_sk ) + WHERE i_category = 'Men' + UNION + SELECT d_year, + i_brand_id, + i_class_id, + i_category_id, + i_manufact_id, + ws_quantity - COALESCE(wr_return_quantity, 0) AS + sales_cnt, + ws_ext_sales_price - COALESCE(wr_return_amt, 0.0) AS + sales_amt + FROM web_sales + JOIN item + ON i_item_sk = ws_item_sk + JOIN date_dim + ON d_date_sk = ws_sold_date_sk + LEFT JOIN web_returns + ON ( ws_order_number = wr_order_number + AND ws_item_sk = wr_item_sk ) + WHERE i_category = 'Men') sales_detail + GROUP BY d_year, + i_brand_id, + i_class_id, + i_category_id, + i_manufact_id) +SELECT prev_yr.d_year AS prev_year, + curr_yr.d_year AS year1, + curr_yr.i_brand_id, + curr_yr.i_class_id, + curr_yr.i_category_id, + curr_yr.i_manufact_id, + prev_yr.sales_cnt AS prev_yr_cnt, + curr_yr.sales_cnt AS curr_yr_cnt, + curr_yr.sales_cnt - prev_yr.sales_cnt AS sales_cnt_diff, + curr_yr.sales_amt - prev_yr.sales_amt AS sales_amt_diff +FROM all_sales curr_yr, + all_sales prev_yr +WHERE curr_yr.i_brand_id = prev_yr.i_brand_id + AND curr_yr.i_class_id = prev_yr.i_class_id + AND curr_yr.i_category_id = prev_yr.i_category_id + AND curr_yr.i_manufact_id = prev_yr.i_manufact_id + AND curr_yr.d_year = 2002 + AND prev_yr.d_year = 2002 - 1 + AND Cast(curr_yr.sales_cnt AS DECIMAL(17, 2)) / Cast(prev_yr.sales_cnt AS + DECIMAL(17, 2)) + < 0.9 +ORDER BY sales_cnt_diff +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query76.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query76.sql new file mode 100644 index 000000000..f298aa015 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query76.sql @@ -0,0 +1,57 @@ +-- start query 76 in stream 0 using template query76.tpl +SELECT channel, + col_name, + d_year, + d_qoy, + i_category, + Count(*) sales_cnt, + Sum(ext_sales_price) sales_amt +FROM (SELECT 'store' AS channel, + 'ss_hdemo_sk' col_name, + d_year, + d_qoy, + i_category, + ss_ext_sales_price ext_sales_price + FROM store_sales, + item, + date_dim + WHERE ss_hdemo_sk IS NULL + AND ss_sold_date_sk = d_date_sk + AND ss_item_sk = i_item_sk + UNION ALL + SELECT 'web' AS channel, + 'ws_ship_hdemo_sk' col_name, + d_year, + d_qoy, + i_category, + ws_ext_sales_price ext_sales_price + FROM web_sales, + item, + date_dim + WHERE ws_ship_hdemo_sk IS NULL + AND ws_sold_date_sk = d_date_sk + AND ws_item_sk = i_item_sk + UNION ALL + SELECT 'catalog' AS channel, + 'cs_warehouse_sk' col_name, + d_year, + d_qoy, + i_category, + cs_ext_sales_price ext_sales_price + FROM catalog_sales, + item, + date_dim + WHERE cs_warehouse_sk IS NULL + AND cs_sold_date_sk = d_date_sk + AND cs_item_sk = i_item_sk) foo +GROUP BY channel, + col_name, + d_year, + d_qoy, + i_category +ORDER BY channel, + col_name, + d_year, + d_qoy, + i_category +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query77.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query77.sql new file mode 100644 index 000000000..ebcea2b12 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query77.sql @@ -0,0 +1,107 @@ + +-- start query 77 in stream 0 using template query77.tpl +WITH ss AS +( + SELECT s_store_sk, + Sum(ss_ext_sales_price) AS sales, + Sum(ss_net_profit) AS profit + FROM store_sales, + date_dim, + store + WHERE ss_sold_date_sk = d_date_sk + AND d_date BETWEEN Cast('2001-08-16' AS DATE) AND ( + Cast('2001-08-16' AS DATE) + INTERVAL '30' day) + AND ss_store_sk = s_store_sk + GROUP BY s_store_sk) , sr AS +( + SELECT s_store_sk, + sum(sr_return_amt) AS returns1, + sum(sr_net_loss) AS profit_loss + FROM store_returns, + date_dim, + store + WHERE sr_returned_date_sk = d_date_sk + AND d_date BETWEEN cast('2001-08-16' AS date) AND ( + cast('2001-08-16' AS date) + INTERVAL '30' day) + AND sr_store_sk = s_store_sk + GROUP BY s_store_sk), cs AS +( + SELECT cs_call_center_sk, + sum(cs_ext_sales_price) AS sales, + sum(cs_net_profit) AS profit + FROM catalog_sales, + date_dim + WHERE cs_sold_date_sk = d_date_sk + AND d_date BETWEEN cast('2001-08-16' AS date) AND ( + cast('2001-08-16' AS date) + INTERVAL '30' day) + GROUP BY cs_call_center_sk ), cr AS +( + SELECT cr_call_center_sk, + sum(cr_return_amount) AS returns1, + sum(cr_net_loss) AS profit_loss + FROM catalog_returns, + date_dim + WHERE cr_returned_date_sk = d_date_sk + AND d_date BETWEEN cast('2001-08-16' AS date) AND ( + cast('2001-08-16' AS date) + INTERVAL '30' day) + GROUP BY cr_call_center_sk ), ws AS +( + SELECT wp_web_page_sk, + sum(ws_ext_sales_price) AS sales, + sum(ws_net_profit) AS profit + FROM web_sales, + date_dim, + web_page + WHERE ws_sold_date_sk = d_date_sk + AND d_date BETWEEN cast('2001-08-16' AS date) AND ( + cast('2001-08-16' AS date) + INTERVAL '30' day) + AND ws_web_page_sk = wp_web_page_sk + GROUP BY wp_web_page_sk), wr AS +( + SELECT wp_web_page_sk, + sum(wr_return_amt) AS returns1, + sum(wr_net_loss) AS profit_loss + FROM web_returns, + date_dim, + web_page + WHERE wr_returned_date_sk = d_date_sk + AND d_date BETWEEN cast('2001-08-16' AS date) AND ( + cast('2001-08-16' AS date) + INTERVAL '30' day) + AND wr_web_page_sk = wp_web_page_sk + GROUP BY wp_web_page_sk) +SELECT + channel , + id , + sum(sales) AS sales , + sum(returns1) AS returns1 , + sum(profit) AS profit +FROM ( + SELECT 'store channel' AS channel , + ss.s_store_sk AS id , + sales , + COALESCE(returns1, 0) AS returns1 , + (profit - COALESCE(profit_loss,0)) AS profit + FROM ss + LEFT JOIN sr + ON ss.s_store_sk = sr.s_store_sk + UNION ALL + SELECT 'catalog channel' AS channel , + cs_call_center_sk AS id , + sales , + returns1 , + (profit - profit_loss) AS profit + FROM cs , + cr + UNION ALL + SELECT 'web channel' AS channel , + ws.wp_web_page_sk AS id , + sales , + COALESCE(returns1, 0) returns1 , + (profit - COALESCE(profit_loss,0)) AS profit + FROM ws + LEFT JOIN wr + ON ws.wp_web_page_sk = wr.wp_web_page_sk ) x +GROUP BY rollup (channel, id) +ORDER BY channel , + id +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query78.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query78.sql new file mode 100644 index 000000000..b57b52389 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query78.sql @@ -0,0 +1,86 @@ +-- start query 78 in stream 0 using template query78.tpl +WITH ws + AS (SELECT d_year AS ws_sold_year, + ws_item_sk, + ws_bill_customer_sk ws_customer_sk, + Sum(ws_quantity) ws_qty, + Sum(ws_wholesale_cost) ws_wc, + Sum(ws_sales_price) ws_sp + FROM web_sales + LEFT JOIN web_returns + ON wr_order_number = ws_order_number + AND ws_item_sk = wr_item_sk + JOIN date_dim + ON ws_sold_date_sk = d_date_sk + WHERE wr_order_number IS NULL + GROUP BY d_year, + ws_item_sk, + ws_bill_customer_sk), + cs + AS (SELECT d_year AS cs_sold_year, + cs_item_sk, + cs_bill_customer_sk cs_customer_sk, + Sum(cs_quantity) cs_qty, + Sum(cs_wholesale_cost) cs_wc, + Sum(cs_sales_price) cs_sp + FROM catalog_sales + LEFT JOIN catalog_returns + ON cr_order_number = cs_order_number + AND cs_item_sk = cr_item_sk + JOIN date_dim + ON cs_sold_date_sk = d_date_sk + WHERE cr_order_number IS NULL + GROUP BY d_year, + cs_item_sk, + cs_bill_customer_sk), + ss + AS (SELECT d_year AS ss_sold_year, + ss_item_sk, + ss_customer_sk, + Sum(ss_quantity) ss_qty, + Sum(ss_wholesale_cost) ss_wc, + Sum(ss_sales_price) ss_sp + FROM store_sales + LEFT JOIN store_returns + ON sr_ticket_number = ss_ticket_number + AND ss_item_sk = sr_item_sk + JOIN date_dim + ON ss_sold_date_sk = d_date_sk + WHERE sr_ticket_number IS NULL + GROUP BY d_year, + ss_item_sk, + ss_customer_sk) +SELECT ss_item_sk, + Round(ss_qty / ( COALESCE(ws_qty + cs_qty, 1) ), 2) ratio, + ss_qty store_qty, + ss_wc + store_wholesale_cost, + ss_sp + store_sales_price, + COALESCE(ws_qty, 0) + COALESCE(cs_qty, 0) + other_chan_qty, + COALESCE(ws_wc, 0) + COALESCE(cs_wc, 0) + other_chan_wholesale_cost, + COALESCE(ws_sp, 0) + COALESCE(cs_sp, 0) + other_chan_sales_price +FROM ss + LEFT JOIN ws + ON ( ws_sold_year = ss_sold_year + AND ws_item_sk = ss_item_sk + AND ws_customer_sk = ss_customer_sk ) + LEFT JOIN cs + ON ( cs_sold_year = ss_sold_year + AND cs_item_sk = cs_item_sk + AND cs_customer_sk = ss_customer_sk ) +WHERE COALESCE(ws_qty, 0) > 0 + AND COALESCE(cs_qty, 0) > 0 + AND ss_sold_year = 1999 +ORDER BY ss_item_sk, + ss_qty DESC, + ss_wc DESC, + ss_sp DESC, + other_chan_qty, + other_chan_wholesale_cost, + other_chan_sales_price, + Round(ss_qty / ( COALESCE(ws_qty + cs_qty, 1) ), 2) +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query79.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query79.sql new file mode 100644 index 000000000..7e8f52d09 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query79.sql @@ -0,0 +1,35 @@ +-- start query 79 in stream 0 using template query79.tpl +SELECT c_last_name, + c_first_name, + Substr(s_city, 1, 30), + ss_ticket_number, + amt, + profit +FROM (SELECT ss_ticket_number, + ss_customer_sk, + store.s_city, + Sum(ss_coupon_amt) amt, + Sum(ss_net_profit) profit + FROM store_sales, + date_dim, + store, + household_demographics + WHERE store_sales.ss_sold_date_sk = date_dim.d_date_sk + AND store_sales.ss_store_sk = store.s_store_sk + AND store_sales.ss_hdemo_sk = household_demographics.hd_demo_sk + AND ( household_demographics.hd_dep_count = 8 + OR household_demographics.hd_vehicle_count > 4 ) + AND date_dim.d_dow = 1 + AND date_dim.d_year IN ( 2000, 2000 + 1, 2000 + 2 ) + AND store.s_number_employees BETWEEN 200 AND 295 + GROUP BY ss_ticket_number, + ss_customer_sk, + ss_addr_sk, + store.s_city) ms, + customer +WHERE ss_customer_sk = c_customer_sk +ORDER BY c_last_name, + c_first_name, + Substr(s_city, 1, 30), + profit +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query8.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query8.sql new file mode 100644 index 000000000..8d98a76a4 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query8.sql @@ -0,0 +1,227 @@ +-- start query 8 in stream 0 using template query8.tpl +SELECT s_store_name, + Sum(ss_net_profit) +FROM store_sales, + date_dim, + store, + (SELECT ca_zip + FROM (SELECT Substr(ca_zip, 1, 5) ca_zip + FROM customer_address + WHERE Substr(ca_zip, 1, 5) IN ( '67436', '26121', '38443', + '63157', + '68856', '19485', '86425', + '26741', + '70991', '60899', '63573', + '47556', + '56193', '93314', '87827', + '62017', + '85067', '95390', '48091', + '10261', + '81845', '41790', '42853', + '24675', + '12840', '60065', '84430', + '57451', + '24021', '91735', '75335', + '71935', + '34482', '56943', '70695', + '52147', + '56251', '28411', '86653', + '23005', + '22478', '29031', '34398', + '15365', + '42460', '33337', '59433', + '73943', + '72477', '74081', '74430', + '64605', + '39006', '11226', '49057', + '97308', + '42663', '18187', '19768', + '43454', + '32147', '76637', '51975', + '11181', + '45630', '33129', '45995', + '64386', + '55522', '26697', '20963', + '35154', + '64587', '49752', '66386', + '30586', + '59286', '13177', '66646', + '84195', + '74316', '36853', '32927', + '12469', + '11904', '36269', '17724', + '55346', + '12595', '53988', '65439', + '28015', + '63268', '73590', '29216', + '82575', + '69267', '13805', '91678', + '79460', + '94152', '14961', '15419', + '48277', + '62588', '55493', '28360', + '14152', + '55225', '18007', '53705', + '56573', + '80245', '71769', '57348', + '36845', + '13039', '17270', '22363', + '83474', + '25294', '43269', '77666', + '15488', + '99146', '64441', '43338', + '38736', + '62754', '48556', '86057', + '23090', + '38114', '66061', '18910', + '84385', + '23600', '19975', '27883', + '65719', + '19933', '32085', '49731', + '40473', + '27190', '46192', '23949', + '44738', + '12436', '64794', '68741', + '15333', + '24282', '49085', '31844', + '71156', + '48441', '17100', '98207', + '44982', + '20277', '71496', '96299', + '37583', + '22206', '89174', '30589', + '61924', + '53079', '10976', '13104', + '42794', + '54772', '15809', '56434', + '39975', + '13874', '30753', '77598', + '78229', + '59478', '12345', '55547', + '57422', + '42600', '79444', '29074', + '29752', + '21676', '32096', '43044', + '39383', + '37296', '36295', '63077', + '16572', + '31275', '18701', '40197', + '48242', + '27219', '49865', '84175', + '30446', + '25165', '13807', '72142', + '70499', + '70464', '71429', '18111', + '70857', + '29545', '36425', '52706', + '36194', + '42963', '75068', '47921', + '74763', + '90990', '89456', '62073', + '88397', + '73963', '75885', '62657', + '12530', + '81146', '57434', '25099', + '41429', + '98441', '48713', '52552', + '31667', + '14072', '13903', '44709', + '85429', + '58017', '38295', '44875', + '73541', + '30091', '12707', '23762', + '62258', + '33247', '78722', '77431', + '14510', + '35656', '72428', '92082', + '35267', + '43759', '24354', '90952', + '11512', + '21242', '22579', '56114', + '32339', + '52282', '41791', '24484', + '95020', + '28408', '99710', '11899', + '43344', + '72915', '27644', '62708', + '74479', + '17177', '32619', '12351', + '91339', + '31169', '57081', '53522', + '16712', + '34419', '71779', '44187', + '46206', + '96099', '61910', '53664', + '12295', + '31837', '33096', '10813', + '63048', + '31732', '79118', '73084', + '72783', + '84952', '46965', '77956', + '39815', + '32311', '75329', '48156', + '30826', + '49661', '13736', '92076', + '74865', + '88149', '92397', '52777', + '68453', + '32012', '21222', '52721', + '24626', + '18210', '42177', '91791', + '75251', + '82075', '44372', '45542', + '20609', + '60115', '17362', '22750', + '90434', + '31852', '54071', '33762', + '14705', + '40718', '56433', '30996', + '40657', + '49056', '23585', '66455', + '41021', + '74736', '72151', '37007', + '21729', + '60177', '84558', '59027', + '93855', + '60022', '86443', '19541', + '86886', + '30532', '39062', '48532', + '34713', + '52077', '22564', '64638', + '15273', + '31677', '36138', '62367', + '60261', + '80213', '42818', '25113', + '72378', + '69802', '69096', '55443', + '28820', + '13848', '78258', '37490', + '30556', + '77380', '28447', '44550', + '26791', + '70609', '82182', '33306', + '43224', + '22322', '86959', '68519', + '14308', + '46501', '81131', '34056', + '61991', + '19896', '87804', '65774', + '92564' ) + INTERSECT + SELECT ca_zip + FROM (SELECT Substr(ca_zip, 1, 5) ca_zip, + Count(*) cnt + FROM customer_address, + customer + WHERE ca_address_sk = c_current_addr_sk + AND c_preferred_cust_flag = 'Y' + GROUP BY ca_zip + HAVING Count(*) > 10)A1)A2) V1 +WHERE ss_store_sk = s_store_sk + AND ss_sold_date_sk = d_date_sk + AND d_qoy = 2 + AND d_year = 2000 + AND ( Substr(s_zip, 1, 2) = Substr(V1.ca_zip, 1, 2) ) +GROUP BY s_store_name +ORDER BY s_store_name +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query80.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query80.sql new file mode 100644 index 000000000..4fcb9cb2f --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query80.sql @@ -0,0 +1,105 @@ +-- start query 80 in stream 0 using template query80.tpl +WITH ssr AS +( + SELECT s_store_id AS store_id, + Sum(ss_ext_sales_price) AS sales, + Sum(COALESCE(sr_return_amt, 0)) AS returns1, + Sum(ss_net_profit - COALESCE(sr_net_loss, 0)) AS profit + FROM store_sales + LEFT OUTER JOIN store_returns + ON ( + ss_item_sk = sr_item_sk + AND ss_ticket_number = sr_ticket_number), + date_dim, + store, + item, + promotion + WHERE ss_sold_date_sk = d_date_sk + AND d_date BETWEEN Cast('2000-08-26' AS DATE) AND ( + Cast('2000-08-26' AS DATE) + INTERVAL '30' day) + AND ss_store_sk = s_store_sk + AND ss_item_sk = i_item_sk + AND i_current_price > 50 + AND ss_promo_sk = p_promo_sk + AND p_channel_tv = 'N' + GROUP BY s_store_id) , csr AS +( + SELECT cp_catalog_page_id AS catalog_page_id, + sum(cs_ext_sales_price) AS sales, + sum(COALESCE(cr_return_amount, 0)) AS returns1, + sum(cs_net_profit - COALESCE(cr_net_loss, 0)) AS profit + FROM catalog_sales + LEFT OUTER JOIN catalog_returns + ON ( + cs_item_sk = cr_item_sk + AND cs_order_number = cr_order_number), + date_dim, + catalog_page, + item, + promotion + WHERE cs_sold_date_sk = d_date_sk + AND d_date BETWEEN cast('2000-08-26' AS date) AND ( + cast('2000-08-26' AS date) + INTERVAL '30' day) + AND cs_catalog_page_sk = cp_catalog_page_sk + AND cs_item_sk = i_item_sk + AND i_current_price > 50 + AND cs_promo_sk = p_promo_sk + AND p_channel_tv = 'N' + GROUP BY cp_catalog_page_id) , wsr AS +( + SELECT web_site_id, + sum(ws_ext_sales_price) AS sales, + sum(COALESCE(wr_return_amt, 0)) AS returns1, + sum(ws_net_profit - COALESCE(wr_net_loss, 0)) AS profit + FROM web_sales + LEFT OUTER JOIN web_returns + ON ( + ws_item_sk = wr_item_sk + AND ws_order_number = wr_order_number), + date_dim, + web_site, + item, + promotion + WHERE ws_sold_date_sk = d_date_sk + AND d_date BETWEEN cast('2000-08-26' AS date) AND ( + cast('2000-08-26' AS date) + INTERVAL '30' day) + AND ws_web_site_sk = web_site_sk + AND ws_item_sk = i_item_sk + AND i_current_price > 50 + AND ws_promo_sk = p_promo_sk + AND p_channel_tv = 'N' + GROUP BY web_site_id) +SELECT + channel , + id , + sum(sales) AS sales , + sum(returns1) AS returns1 , + sum(profit) AS profit +FROM ( + SELECT 'store channel' AS channel , + 'store' + || store_id AS id , + sales , + returns1 , + profit + FROM ssr + UNION ALL + SELECT 'catalog channel' AS channel , + 'catalog_page' + || catalog_page_id AS id , + sales , + returns1 , + profit + FROM csr + UNION ALL + SELECT 'web channel' AS channel , + 'web_site' + || web_site_id AS id , + sales , + returns1 , + profit + FROM wsr ) x +GROUP BY rollup (channel, id) +ORDER BY channel , + id +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query81.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query81.sql new file mode 100644 index 000000000..9be95c5d2 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query81.sql @@ -0,0 +1,56 @@ + +-- start query 81 in stream 0 using template query81.tpl +WITH customer_total_return + AS (SELECT cr_returning_customer_sk AS ctr_customer_sk, + ca_state AS ctr_state, + Sum(cr_return_amt_inc_tax) AS ctr_total_return + FROM catalog_returns, + date_dim, + customer_address + WHERE cr_returned_date_sk = d_date_sk + AND d_year = 1999 + AND cr_returning_addr_sk = ca_address_sk + GROUP BY cr_returning_customer_sk, + ca_state) +SELECT c_customer_id, + c_salutation, + c_first_name, + c_last_name, + ca_street_number, + ca_street_name, + ca_street_type, + ca_suite_number, + ca_city, + ca_county, + ca_state, + ca_zip, + ca_country, + ca_gmt_offset, + ca_location_type, + ctr_total_return +FROM customer_total_return ctr1, + customer_address, + customer +WHERE ctr1.ctr_total_return > (SELECT Avg(ctr_total_return) * 1.2 + FROM customer_total_return ctr2 + WHERE ctr1.ctr_state = ctr2.ctr_state) + AND ca_address_sk = c_current_addr_sk + AND ca_state = 'TX' + AND ctr1.ctr_customer_sk = c_customer_sk +ORDER BY c_customer_id, + c_salutation, + c_first_name, + c_last_name, + ca_street_number, + ca_street_name, + ca_street_type, + ca_suite_number, + ca_city, + ca_county, + ca_state, + ca_zip, + ca_country, + ca_gmt_offset, + ca_location_type, + ctr_total_return +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query82.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query82.sql new file mode 100644 index 000000000..27295a5fb --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query82.sql @@ -0,0 +1,23 @@ + +-- start query 82 in stream 0 using template query82.tpl +SELECT + i_item_id , + i_item_desc , + i_current_price +FROM item, + inventory, + date_dim, + store_sales +WHERE i_current_price BETWEEN 63 AND 63+30 +AND inv_item_sk = i_item_sk +AND d_date_sk=inv_date_sk +AND d_date BETWEEN Cast('1998-04-27' AS DATE) AND ( + Cast('1998-04-27' AS DATE) + INTERVAL '60' day) +AND i_manufact_id IN (57,293,427,320) +AND inv_quantity_on_hand BETWEEN 100 AND 500 +AND ss_item_sk = i_item_sk +GROUP BY i_item_id, + i_item_desc, + i_current_price +ORDER BY i_item_id +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query83.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query83.sql new file mode 100644 index 000000000..b5c4378fa --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query83.sql @@ -0,0 +1,75 @@ +-- start query 83 in stream 0 using template query83.tpl +WITH sr_items + AS (SELECT i_item_id item_id, + Sum(sr_return_quantity) sr_item_qty + FROM store_returns, + item, + date_dim + WHERE sr_item_sk = i_item_sk + AND d_date IN (SELECT d_date + FROM date_dim + WHERE d_week_seq IN (SELECT d_week_seq + FROM date_dim + WHERE + d_date IN ( '1999-06-30', + '1999-08-28', + '1999-11-18' + ))) + AND sr_returned_date_sk = d_date_sk + GROUP BY i_item_id), + cr_items + AS (SELECT i_item_id item_id, + Sum(cr_return_quantity) cr_item_qty + FROM catalog_returns, + item, + date_dim + WHERE cr_item_sk = i_item_sk + AND d_date IN (SELECT d_date + FROM date_dim + WHERE d_week_seq IN (SELECT d_week_seq + FROM date_dim + WHERE + d_date IN ( '1999-06-30', + '1999-08-28', + '1999-11-18' + ))) + AND cr_returned_date_sk = d_date_sk + GROUP BY i_item_id), + wr_items + AS (SELECT i_item_id item_id, + Sum(wr_return_quantity) wr_item_qty + FROM web_returns, + item, + date_dim + WHERE wr_item_sk = i_item_sk + AND d_date IN (SELECT d_date + FROM date_dim + WHERE d_week_seq IN (SELECT d_week_seq + FROM date_dim + WHERE + d_date IN ( '1999-06-30', + '1999-08-28', + '1999-11-18' + ))) + AND wr_returned_date_sk = d_date_sk + GROUP BY i_item_id) +SELECT sr_items.item_id, + sr_item_qty, + sr_item_qty / ( sr_item_qty + cr_item_qty + wr_item_qty ) / 3.0 * + 100 sr_dev, + cr_item_qty, + cr_item_qty / ( sr_item_qty + cr_item_qty + wr_item_qty ) / 3.0 * + 100 cr_dev, + wr_item_qty, + wr_item_qty / ( sr_item_qty + cr_item_qty + wr_item_qty ) / 3.0 * + 100 wr_dev, + ( sr_item_qty + cr_item_qty + wr_item_qty ) / 3.0 + average +FROM sr_items, + cr_items, + wr_items +WHERE sr_items.item_id = cr_items.item_id + AND sr_items.item_id = wr_items.item_id +ORDER BY sr_items.item_id, + sr_item_qty +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query84.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query84.sql new file mode 100644 index 000000000..f7eae1a7e --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query84.sql @@ -0,0 +1,21 @@ +-- start query 84 in stream 0 using template query84.tpl +SELECT c_customer_id AS customer_id, + c_last_name + || ', ' + || c_first_name AS customername +FROM customer, + customer_address, + customer_demographics, + household_demographics, + income_band, + store_returns +WHERE ca_city = 'Green Acres' + AND c_current_addr_sk = ca_address_sk + AND ib_lower_bound >= 54986 + AND ib_upper_bound <= 54986 + 50000 + AND ib_income_band_sk = hd_income_band_sk + AND cd_demo_sk = c_current_cdemo_sk + AND hd_demo_sk = c_current_hdemo_sk + AND sr_cdemo_sk = cd_demo_sk +ORDER BY c_customer_id +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query85.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query85.sql new file mode 100644 index 000000000..be2f68d48 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query85.sql @@ -0,0 +1,52 @@ +-- start query 85 in stream 0 using template query85.tpl +SELECT Substr(r_reason_desc, 1, 20), + Avg(ws_quantity), + Avg(wr_refunded_cash), + Avg(wr_fee) +FROM web_sales, + web_returns, + web_page, + customer_demographics cd1, + customer_demographics cd2, + customer_address, + date_dim, + reason +WHERE ws_web_page_sk = wp_web_page_sk + AND ws_item_sk = wr_item_sk + AND ws_order_number = wr_order_number + AND ws_sold_date_sk = d_date_sk + AND d_year = 2001 + AND cd1.cd_demo_sk = wr_refunded_cdemo_sk + AND cd2.cd_demo_sk = wr_returning_cdemo_sk + AND ca_address_sk = wr_refunded_addr_sk + AND r_reason_sk = wr_reason_sk + AND ( ( cd1.cd_marital_status = 'W' + AND cd1.cd_marital_status = cd2.cd_marital_status + AND cd1.cd_education_status = 'Primary' + AND cd1.cd_education_status = cd2.cd_education_status + AND ws_sales_price BETWEEN 100.00 AND 150.00 ) + OR ( cd1.cd_marital_status = 'D' + AND cd1.cd_marital_status = cd2.cd_marital_status + AND cd1.cd_education_status = 'Secondary' + AND cd1.cd_education_status = cd2.cd_education_status + AND ws_sales_price BETWEEN 50.00 AND 100.00 ) + OR ( cd1.cd_marital_status = 'M' + AND cd1.cd_marital_status = cd2.cd_marital_status + AND cd1.cd_education_status = 'Advanced Degree' + AND cd1.cd_education_status = cd2.cd_education_status + AND ws_sales_price BETWEEN 150.00 AND 200.00 ) ) + AND ( ( ca_country = 'United States' + AND ca_state IN ( 'KY', 'ME', 'IL' ) + AND ws_net_profit BETWEEN 100 AND 200 ) + OR ( ca_country = 'United States' + AND ca_state IN ( 'OK', 'NE', 'MN' ) + AND ws_net_profit BETWEEN 150 AND 300 ) + OR ( ca_country = 'United States' + AND ca_state IN ( 'FL', 'WI', 'KS' ) + AND ws_net_profit BETWEEN 50 AND 250 ) ) +GROUP BY r_reason_desc +ORDER BY Substr(r_reason_desc, 1, 20), + Avg(ws_quantity), + Avg(wr_refunded_cash), + Avg(wr_fee) +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query86.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query86.sql new file mode 100644 index 000000000..ec513d402 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query86.sql @@ -0,0 +1,24 @@ +-- start query 86 in stream 0 using template query86.tpl +SELECT Sum(ws_net_paid) AS total_sum, + i_category, + i_class, + Grouping(i_category) + Grouping(i_class) AS lochierarchy, + Rank() + OVER ( + partition BY Grouping(i_category)+Grouping(i_class), CASE + WHEN Grouping( + i_class) = 0 THEN i_category END + ORDER BY Sum(ws_net_paid) DESC) AS rank_within_parent +FROM web_sales, + date_dim d1, + item +WHERE d1.d_month_seq BETWEEN 1183 AND 1183 + 11 + AND d1.d_date_sk = ws_sold_date_sk + AND i_item_sk = ws_item_sk +GROUP BY rollup( i_category, i_class ) +ORDER BY lochierarchy DESC, + CASE + WHEN lochierarchy = 0 THEN i_category + END, + rank_within_parent +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query87.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query87.sql new file mode 100644 index 000000000..6f58f2e09 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query87.sql @@ -0,0 +1,21 @@ +-- start query 87 in stream 0 using template query87.tpl +select count(*) +from ((select distinct c_last_name, c_first_name, d_date + from store_sales, date_dim, customer + where store_sales.ss_sold_date_sk = date_dim.d_date_sk + and store_sales.ss_customer_sk = customer.c_customer_sk + and d_month_seq between 1188 and 1188+11) + except + (select distinct c_last_name, c_first_name, d_date + from catalog_sales, date_dim, customer + where catalog_sales.cs_sold_date_sk = date_dim.d_date_sk + and catalog_sales.cs_bill_customer_sk = customer.c_customer_sk + and d_month_seq between 1188 and 1188+11) + except + (select distinct c_last_name, c_first_name, d_date + from web_sales, date_dim, customer + where web_sales.ws_sold_date_sk = date_dim.d_date_sk + and web_sales.ws_bill_customer_sk = customer.c_customer_sk + and d_month_seq between 1188 and 1188+11) +) cool_cust +; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query88.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query88.sql new file mode 100644 index 000000000..d1945f341 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query88.sql @@ -0,0 +1,92 @@ +-- start query 88 in stream 0 using template query88.tpl +select * +from + (select count(*) h8_30_to_9 + from store_sales, household_demographics , time_dim, store + where ss_sold_time_sk = time_dim.t_time_sk + and ss_hdemo_sk = household_demographics.hd_demo_sk + and ss_store_sk = s_store_sk + and time_dim.t_hour = 8 + and time_dim.t_minute >= 30 + and ((household_demographics.hd_dep_count = -1 and household_demographics.hd_vehicle_count<=-1+2) or + (household_demographics.hd_dep_count = 2 and household_demographics.hd_vehicle_count<=2+2) or + (household_demographics.hd_dep_count = 3 and household_demographics.hd_vehicle_count<=3+2)) + and store.s_store_name = 'ese') s1, + (select count(*) h9_to_9_30 + from store_sales, household_demographics , time_dim, store + where ss_sold_time_sk = time_dim.t_time_sk + and ss_hdemo_sk = household_demographics.hd_demo_sk + and ss_store_sk = s_store_sk + and time_dim.t_hour = 9 + and time_dim.t_minute < 30 + and ((household_demographics.hd_dep_count = -1 and household_demographics.hd_vehicle_count<=-1+2) or + (household_demographics.hd_dep_count = 2 and household_demographics.hd_vehicle_count<=2+2) or + (household_demographics.hd_dep_count = 3 and household_demographics.hd_vehicle_count<=3+2)) + and store.s_store_name = 'ese') s2, + (select count(*) h9_30_to_10 + from store_sales, household_demographics , time_dim, store + where ss_sold_time_sk = time_dim.t_time_sk + and ss_hdemo_sk = household_demographics.hd_demo_sk + and ss_store_sk = s_store_sk + and time_dim.t_hour = 9 + and time_dim.t_minute >= 30 + and ((household_demographics.hd_dep_count = -1 and household_demographics.hd_vehicle_count<=-1+2) or + (household_demographics.hd_dep_count = 2 and household_demographics.hd_vehicle_count<=2+2) or + (household_demographics.hd_dep_count = 3 and household_demographics.hd_vehicle_count<=3+2)) + and store.s_store_name = 'ese') s3, + (select count(*) h10_to_10_30 + from store_sales, household_demographics , time_dim, store + where ss_sold_time_sk = time_dim.t_time_sk + and ss_hdemo_sk = household_demographics.hd_demo_sk + and ss_store_sk = s_store_sk + and time_dim.t_hour = 10 + and time_dim.t_minute < 30 + and ((household_demographics.hd_dep_count = -1 and household_demographics.hd_vehicle_count<=-1+2) or + (household_demographics.hd_dep_count = 2 and household_demographics.hd_vehicle_count<=2+2) or + (household_demographics.hd_dep_count = 3 and household_demographics.hd_vehicle_count<=3+2)) + and store.s_store_name = 'ese') s4, + (select count(*) h10_30_to_11 + from store_sales, household_demographics , time_dim, store + where ss_sold_time_sk = time_dim.t_time_sk + and ss_hdemo_sk = household_demographics.hd_demo_sk + and ss_store_sk = s_store_sk + and time_dim.t_hour = 10 + and time_dim.t_minute >= 30 + and ((household_demographics.hd_dep_count = -1 and household_demographics.hd_vehicle_count<=-1+2) or + (household_demographics.hd_dep_count = 2 and household_demographics.hd_vehicle_count<=2+2) or + (household_demographics.hd_dep_count = 3 and household_demographics.hd_vehicle_count<=3+2)) + and store.s_store_name = 'ese') s5, + (select count(*) h11_to_11_30 + from store_sales, household_demographics , time_dim, store + where ss_sold_time_sk = time_dim.t_time_sk + and ss_hdemo_sk = household_demographics.hd_demo_sk + and ss_store_sk = s_store_sk + and time_dim.t_hour = 11 + and time_dim.t_minute < 30 + and ((household_demographics.hd_dep_count = -1 and household_demographics.hd_vehicle_count<=-1+2) or + (household_demographics.hd_dep_count = 2 and household_demographics.hd_vehicle_count<=2+2) or + (household_demographics.hd_dep_count = 3 and household_demographics.hd_vehicle_count<=3+2)) + and store.s_store_name = 'ese') s6, + (select count(*) h11_30_to_12 + from store_sales, household_demographics , time_dim, store + where ss_sold_time_sk = time_dim.t_time_sk + and ss_hdemo_sk = household_demographics.hd_demo_sk + and ss_store_sk = s_store_sk + and time_dim.t_hour = 11 + and time_dim.t_minute >= 30 + and ((household_demographics.hd_dep_count = -1 and household_demographics.hd_vehicle_count<=-1+2) or + (household_demographics.hd_dep_count = 2 and household_demographics.hd_vehicle_count<=2+2) or + (household_demographics.hd_dep_count = 3 and household_demographics.hd_vehicle_count<=3+2)) + and store.s_store_name = 'ese') s7, + (select count(*) h12_to_12_30 + from store_sales, household_demographics , time_dim, store + where ss_sold_time_sk = time_dim.t_time_sk + and ss_hdemo_sk = household_demographics.hd_demo_sk + and ss_store_sk = s_store_sk + and time_dim.t_hour = 12 + and time_dim.t_minute < 30 + and ((household_demographics.hd_dep_count = -1 and household_demographics.hd_vehicle_count<=-1+2) or + (household_demographics.hd_dep_count = 2 and household_demographics.hd_vehicle_count<=2+2) or + (household_demographics.hd_dep_count = 3 and household_demographics.hd_vehicle_count<=3+2)) + and store.s_store_name = 'ese') s8 +; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query89.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query89.sql new file mode 100644 index 000000000..1459f9cf9 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query89.sql @@ -0,0 +1,40 @@ +-- start query 89 in stream 0 using template query89.tpl +SELECT * +FROM (SELECT i_category, + i_class, + i_brand, + s_store_name, + s_company_name, + d_moy, + Sum(ss_sales_price) sum_sales, + Avg(Sum(ss_sales_price)) + OVER ( + partition BY i_category, i_brand, s_store_name, s_company_name + ) + avg_monthly_sales + FROM item, + store_sales, + date_dim, + store + WHERE ss_item_sk = i_item_sk + AND ss_sold_date_sk = d_date_sk + AND ss_store_sk = s_store_sk + AND d_year IN ( 2002 ) + AND ( ( i_category IN ( 'Home', 'Men', 'Sports' ) + AND i_class IN ( 'paint', 'accessories', 'fitness' ) ) + OR ( i_category IN ( 'Shoes', 'Jewelry', 'Women' ) + AND i_class IN ( 'mens', 'pendants', 'swimwear' ) ) ) + GROUP BY i_category, + i_class, + i_brand, + s_store_name, + s_company_name, + d_moy) tmp1 +WHERE CASE + WHEN ( avg_monthly_sales <> 0 ) THEN ( + Abs(sum_sales - avg_monthly_sales) / avg_monthly_sales ) + ELSE NULL + END > 0.1 +ORDER BY sum_sales - avg_monthly_sales, + s_store_name +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query9.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query9.sql new file mode 100644 index 000000000..8073df2f9 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query9.sql @@ -0,0 +1,63 @@ +-- start query 9 in stream 0 using template query9.tpl +SELECT CASE + WHEN (SELECT Count(*) + FROM store_sales + WHERE ss_quantity BETWEEN 1 AND 20) > 3672 THEN + (SELECT Avg(ss_ext_list_price) + FROM store_sales + WHERE + ss_quantity BETWEEN 1 AND 20) + ELSE (SELECT Avg(ss_net_profit) + FROM store_sales + WHERE ss_quantity BETWEEN 1 AND 20) + END bucket1, + CASE + WHEN (SELECT Count(*) + FROM store_sales + WHERE ss_quantity BETWEEN 21 AND 40) > 3392 THEN + (SELECT Avg(ss_ext_list_price) + FROM store_sales + WHERE + ss_quantity BETWEEN 21 AND 40) + ELSE (SELECT Avg(ss_net_profit) + FROM store_sales + WHERE ss_quantity BETWEEN 21 AND 40) + END bucket2, + CASE + WHEN (SELECT Count(*) + FROM store_sales + WHERE ss_quantity BETWEEN 41 AND 60) > 32784 THEN + (SELECT Avg(ss_ext_list_price) + FROM store_sales + WHERE + ss_quantity BETWEEN 41 AND 60) + ELSE (SELECT Avg(ss_net_profit) + FROM store_sales + WHERE ss_quantity BETWEEN 41 AND 60) + END bucket3, + CASE + WHEN (SELECT Count(*) + FROM store_sales + WHERE ss_quantity BETWEEN 61 AND 80) > 26032 THEN + (SELECT Avg(ss_ext_list_price) + FROM store_sales + WHERE + ss_quantity BETWEEN 61 AND 80) + ELSE (SELECT Avg(ss_net_profit) + FROM store_sales + WHERE ss_quantity BETWEEN 61 AND 80) + END bucket4, + CASE + WHEN (SELECT Count(*) + FROM store_sales + WHERE ss_quantity BETWEEN 81 AND 100) > 23982 THEN + (SELECT Avg(ss_ext_list_price) + FROM store_sales + WHERE + ss_quantity BETWEEN 81 AND 100) + ELSE (SELECT Avg(ss_net_profit) + FROM store_sales + WHERE ss_quantity BETWEEN 81 AND 100) + END bucket5 +FROM reason +WHERE r_reason_sk = 1; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query90.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query90.sql new file mode 100644 index 000000000..bc117f29e --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query90.sql @@ -0,0 +1,28 @@ + +-- start query 90 in stream 0 using template query90.tpl +SELECT Cast(amc AS DECIMAL(15, 4)) / Cast(pmc AS DECIMAL(15, 4)) + am_pm_ratio +FROM (SELECT Count(*) amc + FROM web_sales, + household_demographics, + time_dim, + web_page + WHERE ws_sold_time_sk = time_dim.t_time_sk + AND ws_ship_hdemo_sk = household_demographics.hd_demo_sk + AND ws_web_page_sk = web_page.wp_web_page_sk + AND time_dim.t_hour BETWEEN 12 AND 12 + 1 + AND household_demographics.hd_dep_count = 8 + AND web_page.wp_char_count BETWEEN 5000 AND 5200) at1, + (SELECT Count(*) pmc + FROM web_sales, + household_demographics, + time_dim, + web_page + WHERE ws_sold_time_sk = time_dim.t_time_sk + AND ws_ship_hdemo_sk = household_demographics.hd_demo_sk + AND ws_web_page_sk = web_page.wp_web_page_sk + AND time_dim.t_hour BETWEEN 20 AND 20 + 1 + AND household_demographics.hd_dep_count = 8 + AND web_page.wp_char_count BETWEEN 5000 AND 5200) pt +ORDER BY am_pm_ratio +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query91.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query91.sql new file mode 100644 index 000000000..457aa8b45 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query91.sql @@ -0,0 +1,32 @@ +-- start query 91 in stream 0 using template query91.tpl +SELECT cc_call_center_id Call_Center, + cc_name Call_Center_Name, + cc_manager Manager, + Sum(cr_net_loss) Returns_Loss +FROM call_center, + catalog_returns, + date_dim, + customer, + customer_address, + customer_demographics, + household_demographics +WHERE cr_call_center_sk = cc_call_center_sk + AND cr_returned_date_sk = d_date_sk + AND cr_returning_customer_sk = c_customer_sk + AND cd_demo_sk = c_current_cdemo_sk + AND hd_demo_sk = c_current_hdemo_sk + AND ca_address_sk = c_current_addr_sk + AND d_year = 1999 + AND d_moy = 12 + AND ( ( cd_marital_status = 'M' + AND cd_education_status = 'Unknown' ) + OR ( cd_marital_status = 'W' + AND cd_education_status = 'Advanced Degree' ) ) + AND hd_buy_potential LIKE 'Unknown%' + AND ca_gmt_offset = -7 +GROUP BY cc_call_center_id, + cc_name, + cc_manager, + cd_marital_status, + cd_education_status +ORDER BY Sum(cr_net_loss) DESC; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query92.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query92.sql new file mode 100644 index 000000000..2c18f4219 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query92.sql @@ -0,0 +1,22 @@ +-- start query 92 in stream 0 using template query92.tpl +SELECT + Sum(ws_ext_discount_amt) AS "Excess Discount Amount" +FROM web_sales , + item , + date_dim +WHERE i_manufact_id = 718 +AND i_item_sk = ws_item_sk +AND d_date BETWEEN '2002-03-29' AND ( + Cast('2002-03-29' AS DATE) + INTERVAL '90' day) +AND d_date_sk = ws_sold_date_sk +AND ws_ext_discount_amt > + ( + SELECT 1.3 * avg(ws_ext_discount_amt) + FROM web_sales , + date_dim + WHERE ws_item_sk = i_item_sk + AND d_date BETWEEN '2002-03-29' AND ( + cast('2002-03-29' AS date) + INTERVAL '90' day) + AND d_date_sk = ws_sold_date_sk ) +ORDER BY sum(ws_ext_discount_amt) +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query93.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query93.sql new file mode 100644 index 000000000..f7fdc3296 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query93.sql @@ -0,0 +1,22 @@ +-- start query 93 in stream 0 using template query93.tpl +SELECT ss_customer_sk, + Sum(act_sales) sumsales +FROM (SELECT ss_item_sk, + ss_ticket_number, + ss_customer_sk, + CASE + WHEN sr_return_quantity IS NOT NULL THEN + ( ss_quantity - sr_return_quantity ) * ss_sales_price + ELSE ( ss_quantity * ss_sales_price ) + END act_sales + FROM store_sales + LEFT OUTER JOIN store_returns + ON ( sr_item_sk = ss_item_sk + AND sr_ticket_number = ss_ticket_number ), + reason + WHERE sr_reason_sk = r_reason_sk + AND r_reason_desc = 'reason 38') t +GROUP BY ss_customer_sk +ORDER BY sumsales, + ss_customer_sk +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query94.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query94.sql new file mode 100644 index 000000000..773fa37aa --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query94.sql @@ -0,0 +1,29 @@ +-- start query 94 in stream 0 using template query94.tpl +SELECT + Count(DISTINCT ws_order_number) AS "order count" , + Sum(ws_ext_ship_cost) AS "total shipping cost" , + Sum(ws_net_profit) AS "total net profit" +FROM web_sales ws1 , + date_dim , + customer_address , + web_site +WHERE d_date BETWEEN '2000-3-01' AND ( + Cast('2000-3-01' AS DATE) + INTERVAL '60' day) +AND ws1.ws_ship_date_sk = d_date_sk +AND ws1.ws_ship_addr_sk = ca_address_sk +AND ca_state = 'MT' +AND ws1.ws_web_site_sk = web_site_sk +AND web_company_name = 'pri' +AND EXISTS + ( + SELECT * + FROM web_sales ws2 + WHERE ws1.ws_order_number = ws2.ws_order_number + AND ws1.ws_warehouse_sk <> ws2.ws_warehouse_sk) +AND NOT EXISTS + ( + SELECT * + FROM web_returns wr1 + WHERE ws1.ws_order_number = wr1.wr_order_number) +ORDER BY count(DISTINCT ws_order_number) +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query95.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query95.sql new file mode 100644 index 000000000..334a40529 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query95.sql @@ -0,0 +1,37 @@ +-- start query 95 in stream 0 using template query95.tpl +WITH ws_wh AS +( + SELECT ws1.ws_order_number, + ws1.ws_warehouse_sk wh1, + ws2.ws_warehouse_sk wh2 + FROM web_sales ws1, + web_sales ws2 + WHERE ws1.ws_order_number = ws2.ws_order_number + AND ws1.ws_warehouse_sk <> ws2.ws_warehouse_sk) +SELECT + Count(DISTINCT ws_order_number) AS "order count" , + Sum(ws_ext_ship_cost) AS "total shipping cost" , + Sum(ws_net_profit) AS "total net profit" +FROM web_sales ws1 , + date_dim , + customer_address , + web_site +WHERE d_date BETWEEN '2000-4-01' AND ( + Cast('2000-4-01' AS DATE) + INTERVAL '60' day) +AND ws1.ws_ship_date_sk = d_date_sk +AND ws1.ws_ship_addr_sk = ca_address_sk +AND ca_state = 'IN' +AND ws1.ws_web_site_sk = web_site_sk +AND web_company_name = 'pri' +AND ws1.ws_order_number IN + ( + SELECT ws_order_number + FROM ws_wh) +AND ws1.ws_order_number IN + ( + SELECT wr_order_number + FROM web_returns, + ws_wh + WHERE wr_order_number = ws_wh.ws_order_number) +ORDER BY count(DISTINCT ws_order_number) +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query96.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query96.sql new file mode 100644 index 000000000..1f7731524 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query96.sql @@ -0,0 +1,15 @@ +-- start query 96 in stream 0 using template query96.tpl +SELECT Count(*) +FROM store_sales, + household_demographics, + time_dim, + store +WHERE ss_sold_time_sk = time_dim.t_time_sk + AND ss_hdemo_sk = household_demographics.hd_demo_sk + AND ss_store_sk = s_store_sk + AND time_dim.t_hour = 15 + AND time_dim.t_minute >= 30 + AND household_demographics.hd_dep_count = 7 + AND store.s_store_name = 'ese' +ORDER BY Count(*) +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query97.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query97.sql new file mode 100644 index 000000000..6a6be875a --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query97.sql @@ -0,0 +1,40 @@ + +-- start query 97 in stream 0 using template query97.tpl +WITH ssci + AS (SELECT ss_customer_sk customer_sk, + ss_item_sk item_sk + FROM store_sales, + date_dim + WHERE ss_sold_date_sk = d_date_sk + AND d_month_seq BETWEEN 1196 AND 1196 + 11 + GROUP BY ss_customer_sk, + ss_item_sk), + csci + AS (SELECT cs_bill_customer_sk customer_sk, + cs_item_sk item_sk + FROM catalog_sales, + date_dim + WHERE cs_sold_date_sk = d_date_sk + AND d_month_seq BETWEEN 1196 AND 1196 + 11 + GROUP BY cs_bill_customer_sk, + cs_item_sk) +SELECT Sum(CASE + WHEN ssci.customer_sk IS NOT NULL + AND csci.customer_sk IS NULL THEN 1 + ELSE 0 + END) store_only, + Sum(CASE + WHEN ssci.customer_sk IS NULL + AND csci.customer_sk IS NOT NULL THEN 1 + ELSE 0 + END) catalog_only, + Sum(CASE + WHEN ssci.customer_sk IS NOT NULL + AND csci.customer_sk IS NOT NULL THEN 1 + ELSE 0 + END) store_and_catalog +FROM ssci + FULL OUTER JOIN csci + ON ( ssci.customer_sk = csci.customer_sk + AND ssci.item_sk = csci.item_sk ) +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query98.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query98.sql new file mode 100644 index 000000000..62eaaa518 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query98.sql @@ -0,0 +1,29 @@ + +-- start query 98 in stream 0 using template query98.tpl +SELECT i_item_id, + i_item_desc, + i_category, + i_class, + i_current_price, + Sum(ss_ext_sales_price) AS itemrevenue, + Sum(ss_ext_sales_price) * 100 / Sum(Sum(ss_ext_sales_price)) + OVER ( + PARTITION BY i_class) AS revenueratio +FROM store_sales, + item, + date_dim +WHERE ss_item_sk = i_item_sk + AND i_category IN ( 'Men', 'Home', 'Electronics' ) + AND ss_sold_date_sk = d_date_sk + AND d_date BETWEEN CAST('2000-05-18' AS DATE) AND ( + CAST('2000-05-18' AS DATE) + INTERVAL '30' DAY ) +GROUP BY i_item_id, + i_item_desc, + i_category, + i_class, + i_current_price +ORDER BY i_category, + i_class, + i_item_id, + i_item_desc, + revenueratio; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query99.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query99.sql new file mode 100644 index 000000000..73b6739e5 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/ansi/query99.sql @@ -0,0 +1,47 @@ + + +-- start query 99 in stream 0 using template query99.tpl +SELECT Substr(w_warehouse_name, 1, 20), + sm_type, + cc_name, + Sum(CASE + WHEN ( cs_ship_date_sk - cs_sold_date_sk <= 30 ) THEN 1 + ELSE 0 + END) AS "30 days", + Sum(CASE + WHEN ( cs_ship_date_sk - cs_sold_date_sk > 30 ) + AND ( cs_ship_date_sk - cs_sold_date_sk <= 60 ) THEN 1 + ELSE 0 + END) AS "31-60 days", + Sum(CASE + WHEN ( cs_ship_date_sk - cs_sold_date_sk > 60 ) + AND ( cs_ship_date_sk - cs_sold_date_sk <= 90 ) THEN 1 + ELSE 0 + END) AS "61-90 days", + Sum(CASE + WHEN ( cs_ship_date_sk - cs_sold_date_sk > 90 ) + AND ( cs_ship_date_sk - cs_sold_date_sk <= 120 ) THEN + 1 + ELSE 0 + END) AS "91-120 days", + Sum(CASE + WHEN ( cs_ship_date_sk - cs_sold_date_sk > 120 ) THEN 1 + ELSE 0 + END) AS ">120 days" +FROM catalog_sales, + warehouse, + ship_mode, + call_center, + date_dim +WHERE d_month_seq BETWEEN 1200 AND 1200 + 11 + AND cs_ship_date_sk = d_date_sk + AND cs_warehouse_sk = w_warehouse_sk + AND cs_ship_mode_sk = sm_ship_mode_sk + AND cs_call_center_sk = cc_call_center_sk +GROUP BY Substr(w_warehouse_name, 1, 20), + sm_type, + cc_name +ORDER BY Substr(w_warehouse_name, 1, 20), + sm_type, + cc_name +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query1.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query1.sql new file mode 100644 index 000000000..8808e3ce6 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query1.sql @@ -0,0 +1,23 @@ +-- start query 1 in stream 0 using template query1.tpl +WITH customer_total_return + AS (SELECT sr_customer_sk AS ctr_customer_sk, + sr_store_sk AS ctr_store_sk, + Sum(sr_return_amt) AS ctr_total_return + FROM store_returns, + date_dim + WHERE sr_returned_date_sk = d_date_sk + AND d_year = 2001 + GROUP BY sr_customer_sk, + sr_store_sk) +SELECT c_customer_id +FROM customer_total_return ctr1, + store, + customer +WHERE ctr1.ctr_total_return > (SELECT Avg(ctr_total_return) * 1.2 + FROM customer_total_return ctr2 + WHERE ctr1.ctr_store_sk = ctr2.ctr_store_sk) + AND s_store_sk = ctr1.ctr_store_sk + AND s_state = 'TN' + AND ctr1.ctr_customer_sk = c_customer_sk +ORDER BY c_customer_id +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query10.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query10.sql new file mode 100644 index 000000000..0cb0ba050 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query10.sql @@ -0,0 +1,62 @@ +-- start query 10 in stream 0 using template query10.tpl +SELECT cd_gender, + cd_marital_status, + cd_education_status, + Count(*) cnt1, + cd_purchase_estimate, + Count(*) cnt2, + cd_credit_rating, + Count(*) cnt3, + cd_dep_count, + Count(*) cnt4, + cd_dep_employed_count, + Count(*) cnt5, + cd_dep_college_count, + Count(*) cnt6 +FROM customer c, + customer_address ca, + customer_demographics +WHERE c.c_current_addr_sk = ca.ca_address_sk + AND ca_county IN ( 'Lycoming County', 'Sheridan County', + 'Kandiyohi County', + 'Pike County', + 'Greene County' ) + AND cd_demo_sk = c.c_current_cdemo_sk + AND EXISTS (SELECT * + FROM store_sales, + date_dim + WHERE c.c_customer_sk = ss_customer_sk + AND ss_sold_date_sk = d_date_sk + AND d_year = 2002 + AND d_moy BETWEEN 4 AND 4 + 3) + AND ( EXISTS (SELECT * + FROM web_sales, + date_dim + WHERE c.c_customer_sk = ws_bill_customer_sk + AND ws_sold_date_sk = d_date_sk + AND d_year = 2002 + AND d_moy BETWEEN 4 AND 4 + 3) + OR EXISTS (SELECT * + FROM catalog_sales, + date_dim + WHERE c.c_customer_sk = cs_ship_customer_sk + AND cs_sold_date_sk = d_date_sk + AND d_year = 2002 + AND d_moy BETWEEN 4 AND 4 + 3) ) +GROUP BY cd_gender, + cd_marital_status, + cd_education_status, + cd_purchase_estimate, + cd_credit_rating, + cd_dep_count, + cd_dep_employed_count, + cd_dep_college_count +ORDER BY cd_gender, + cd_marital_status, + cd_education_status, + cd_purchase_estimate, + cd_credit_rating, + cd_dep_count, + cd_dep_employed_count, + cd_dep_college_count +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query11.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query11.sql new file mode 100644 index 000000000..cf24b9b74 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query11.sql @@ -0,0 +1,97 @@ +-- start query 11 in stream 0 using template query11.tpl +WITH year_total + AS (SELECT c_customer_id customer_id, + c_first_name customer_first_name + , + c_last_name + customer_last_name, + c_preferred_cust_flag + customer_preferred_cust_flag + , + c_birth_country + customer_birth_country, + c_login customer_login, + c_email_address + customer_email_address, + d_year dyear, + Sum(ss_ext_list_price - ss_ext_discount_amt) year_total, + 's' sale_type + FROM customer, + store_sales, + date_dim + WHERE c_customer_sk = ss_customer_sk + AND ss_sold_date_sk = d_date_sk + GROUP BY c_customer_id, + c_first_name, + c_last_name, + c_preferred_cust_flag, + c_birth_country, + c_login, + c_email_address, + d_year + UNION ALL + SELECT c_customer_id customer_id, + c_first_name customer_first_name + , + c_last_name + customer_last_name, + c_preferred_cust_flag + customer_preferred_cust_flag + , + c_birth_country + customer_birth_country, + c_login customer_login, + c_email_address + customer_email_address, + d_year dyear, + Sum(ws_ext_list_price - ws_ext_discount_amt) year_total, + 'w' sale_type + FROM customer, + web_sales, + date_dim + WHERE c_customer_sk = ws_bill_customer_sk + AND ws_sold_date_sk = d_date_sk + GROUP BY c_customer_id, + c_first_name, + c_last_name, + c_preferred_cust_flag, + c_birth_country, + c_login, + c_email_address, + d_year) +SELECT t_s_secyear.customer_id, + t_s_secyear.customer_first_name, + t_s_secyear.customer_last_name, + t_s_secyear.customer_birth_country +FROM year_total t_s_firstyear, + year_total t_s_secyear, + year_total t_w_firstyear, + year_total t_w_secyear +WHERE t_s_secyear.customer_id = t_s_firstyear.customer_id + AND t_s_firstyear.customer_id = t_w_secyear.customer_id + AND t_s_firstyear.customer_id = t_w_firstyear.customer_id + AND t_s_firstyear.sale_type = 's' + AND t_w_firstyear.sale_type = 'w' + AND t_s_secyear.sale_type = 's' + AND t_w_secyear.sale_type = 'w' + AND t_s_firstyear.dyear = 2001 + AND t_s_secyear.dyear = 2001 + 1 + AND t_w_firstyear.dyear = 2001 + AND t_w_secyear.dyear = 2001 + 1 + AND t_s_firstyear.year_total > 0 + AND t_w_firstyear.year_total > 0 + AND CASE + WHEN t_w_firstyear.year_total > 0 THEN t_w_secyear.year_total / + t_w_firstyear.year_total + ELSE 0.0 + END > CASE + WHEN t_s_firstyear.year_total > 0 THEN + t_s_secyear.year_total / + t_s_firstyear.year_total + ELSE 0.0 + END +ORDER BY t_s_secyear.customer_id, + t_s_secyear.customer_first_name, + t_s_secyear.customer_last_name, + t_s_secyear.customer_birth_country +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query12.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query12.sql new file mode 100644 index 000000000..5f6d721c4 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query12.sql @@ -0,0 +1,30 @@ +-- start query 12 in stream 0 using template query12.tpl +SELECT + i_item_id , + i_item_desc , + i_category , + i_class , + i_current_price , + Sum(ws_ext_sales_price) AS itemrevenue , + Sum(ws_ext_sales_price)*100/Sum(Sum(ws_ext_sales_price)) OVER (partition BY i_class) AS revenueratio +FROM web_sales , + item , + date_dim +WHERE ws_item_sk = i_item_sk +AND i_category IN ('Home', + 'Men', + 'Women') +AND ws_sold_date_sk = d_date_sk +AND d_date BETWEEN Cast('2000-05-11' AS DATE) AND ( + Cast('2000-05-11' AS DATE) + INTERVAL '30' day) +GROUP BY i_item_id , + i_item_desc , + i_category , + i_class , + i_current_price +ORDER BY i_category , + i_class , + i_item_id , + i_item_desc , + revenueratio +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query13.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query13.sql new file mode 100644 index 000000000..2bec54b95 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query13.sql @@ -0,0 +1,44 @@ +-- start query 13 in stream 0 using template query13.tpl +SELECT Avg(ss_quantity), + Avg(ss_ext_sales_price), + Avg(ss_ext_wholesale_cost), + Sum(ss_ext_wholesale_cost) +FROM store_sales, + store, + customer_demographics, + household_demographics, + customer_address, + date_dim +WHERE s_store_sk = ss_store_sk + AND ss_sold_date_sk = d_date_sk + AND d_year = 2001 + AND ( ( ss_hdemo_sk = hd_demo_sk + AND cd_demo_sk = ss_cdemo_sk + AND cd_marital_status = 'U' + AND cd_education_status = 'Advanced Degree' + AND ss_sales_price BETWEEN 100.00 AND 150.00 + AND hd_dep_count = 3 ) + OR ( ss_hdemo_sk = hd_demo_sk + AND cd_demo_sk = ss_cdemo_sk + AND cd_marital_status = 'M' + AND cd_education_status = 'Primary' + AND ss_sales_price BETWEEN 50.00 AND 100.00 + AND hd_dep_count = 1 ) + OR ( ss_hdemo_sk = hd_demo_sk + AND cd_demo_sk = ss_cdemo_sk + AND cd_marital_status = 'D' + AND cd_education_status = 'Secondary' + AND ss_sales_price BETWEEN 150.00 AND 200.00 + AND hd_dep_count = 1 ) ) + AND ( ( ss_addr_sk = ca_address_sk + AND ca_country = 'United States' + AND ca_state IN ( 'AZ', 'NE', 'IA' ) + AND ss_net_profit BETWEEN 100 AND 200 ) + OR ( ss_addr_sk = ca_address_sk + AND ca_country = 'United States' + AND ca_state IN ( 'MS', 'CA', 'NV' ) + AND ss_net_profit BETWEEN 150 AND 300 ) + OR ( ss_addr_sk = ca_address_sk + AND ca_country = 'United States' + AND ca_state IN ( 'GA', 'TX', 'NJ' ) + AND ss_net_profit BETWEEN 50 AND 250 ) ); diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query14.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query14.sql new file mode 100644 index 000000000..7a7572bba --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query14.sql @@ -0,0 +1,245 @@ +-- start query 14 in stream 0 using template query14.tpl +WITH cross_items + AS (SELECT i_item_sk ss_item_sk + FROM item, + (SELECT iss.i_brand_id brand_id, + iss.i_class_id class_id, + iss.i_category_id category_id + FROM store_sales, + item iss, + date_dim d1 + WHERE ss_item_sk = iss.i_item_sk + AND ss_sold_date_sk = d1.d_date_sk + AND d1.d_year BETWEEN 1999 AND 1999 + 2 + INTERSECT + SELECT ics.i_brand_id, + ics.i_class_id, + ics.i_category_id + FROM catalog_sales, + item ics, + date_dim d2 + WHERE cs_item_sk = ics.i_item_sk + AND cs_sold_date_sk = d2.d_date_sk + AND d2.d_year BETWEEN 1999 AND 1999 + 2 + INTERSECT + SELECT iws.i_brand_id, + iws.i_class_id, + iws.i_category_id + FROM web_sales, + item iws, + date_dim d3 + WHERE ws_item_sk = iws.i_item_sk + AND ws_sold_date_sk = d3.d_date_sk + AND d3.d_year BETWEEN 1999 AND 1999 + 2) + WHERE i_brand_id = brand_id + AND i_class_id = class_id + AND i_category_id = category_id), + avg_sales + AS (SELECT Avg(quantity * list_price) average_sales + FROM (SELECT ss_quantity quantity, + ss_list_price list_price + FROM store_sales, + date_dim + WHERE ss_sold_date_sk = d_date_sk + AND d_year BETWEEN 1999 AND 1999 + 2 + UNION ALL + SELECT cs_quantity quantity, + cs_list_price list_price + FROM catalog_sales, + date_dim + WHERE cs_sold_date_sk = d_date_sk + AND d_year BETWEEN 1999 AND 1999 + 2 + UNION ALL + SELECT ws_quantity quantity, + ws_list_price list_price + FROM web_sales, + date_dim + WHERE ws_sold_date_sk = d_date_sk + AND d_year BETWEEN 1999 AND 1999 + 2) x) +SELECT channel, + i_brand_id, + i_class_id, + i_category_id, + Sum(sales), + Sum(number_sales) +FROM (SELECT 'store' channel, + i_brand_id, + i_class_id, + i_category_id, + Sum(ss_quantity * ss_list_price) sales, + Count(*) number_sales + FROM store_sales, + item, + date_dim + WHERE ss_item_sk IN (SELECT ss_item_sk + FROM cross_items) + AND ss_item_sk = i_item_sk + AND ss_sold_date_sk = d_date_sk + AND d_year = 1999 + 2 + AND d_moy = 11 + GROUP BY i_brand_id, + i_class_id, + i_category_id + HAVING Sum(ss_quantity * ss_list_price) > (SELECT average_sales + FROM avg_sales) + UNION ALL + SELECT 'catalog' channel, + i_brand_id, + i_class_id, + i_category_id, + Sum(cs_quantity * cs_list_price) sales, + Count(*) number_sales + FROM catalog_sales, + item, + date_dim + WHERE cs_item_sk IN (SELECT ss_item_sk + FROM cross_items) + AND cs_item_sk = i_item_sk + AND cs_sold_date_sk = d_date_sk + AND d_year = 1999 + 2 + AND d_moy = 11 + GROUP BY i_brand_id, + i_class_id, + i_category_id + HAVING Sum(cs_quantity * cs_list_price) > (SELECT average_sales + FROM avg_sales) + UNION ALL + SELECT 'web' channel, + i_brand_id, + i_class_id, + i_category_id, + Sum(ws_quantity * ws_list_price) sales, + Count(*) number_sales + FROM web_sales, + item, + date_dim + WHERE ws_item_sk IN (SELECT ss_item_sk + FROM cross_items) + AND ws_item_sk = i_item_sk + AND ws_sold_date_sk = d_date_sk + AND d_year = 1999 + 2 + AND d_moy = 11 + GROUP BY i_brand_id, + i_class_id, + i_category_id + HAVING Sum(ws_quantity * ws_list_price) > (SELECT average_sales + FROM avg_sales)) y +GROUP BY rollup ( channel, i_brand_id, i_class_id, i_category_id ) +ORDER BY channel, + i_brand_id, + i_class_id, + i_category_id +LIMIT 100; + +WITH cross_items + AS (SELECT i_item_sk ss_item_sk + FROM item, + (SELECT iss.i_brand_id brand_id, + iss.i_class_id class_id, + iss.i_category_id category_id + FROM store_sales, + item iss, + date_dim d1 + WHERE ss_item_sk = iss.i_item_sk + AND ss_sold_date_sk = d1.d_date_sk + AND d1.d_year BETWEEN 1999 AND 1999 + 2 + INTERSECT + SELECT ics.i_brand_id, + ics.i_class_id, + ics.i_category_id + FROM catalog_sales, + item ics, + date_dim d2 + WHERE cs_item_sk = ics.i_item_sk + AND cs_sold_date_sk = d2.d_date_sk + AND d2.d_year BETWEEN 1999 AND 1999 + 2 + INTERSECT + SELECT iws.i_brand_id, + iws.i_class_id, + iws.i_category_id + FROM web_sales, + item iws, + date_dim d3 + WHERE ws_item_sk = iws.i_item_sk + AND ws_sold_date_sk = d3.d_date_sk + AND d3.d_year BETWEEN 1999 AND 1999 + 2) x + WHERE i_brand_id = brand_id + AND i_class_id = class_id + AND i_category_id = category_id), + avg_sales + AS (SELECT Avg(quantity * list_price) average_sales + FROM (SELECT ss_quantity quantity, + ss_list_price list_price + FROM store_sales, + date_dim + WHERE ss_sold_date_sk = d_date_sk + AND d_year BETWEEN 1999 AND 1999 + 2 + UNION ALL + SELECT cs_quantity quantity, + cs_list_price list_price + FROM catalog_sales, + date_dim + WHERE cs_sold_date_sk = d_date_sk + AND d_year BETWEEN 1999 AND 1999 + 2 + UNION ALL + SELECT ws_quantity quantity, + ws_list_price list_price + FROM web_sales, + date_dim + WHERE ws_sold_date_sk = d_date_sk + AND d_year BETWEEN 1999 AND 1999 + 2) x) +SELECT * +FROM (SELECT 'store' channel, + i_brand_id, + i_class_id, + i_category_id, + Sum(ss_quantity * ss_list_price) sales, + Count(*) number_sales + FROM store_sales, + item, + date_dim + WHERE ss_item_sk IN (SELECT ss_item_sk + FROM cross_items) + AND ss_item_sk = i_item_sk + AND ss_sold_date_sk = d_date_sk + AND d_week_seq = (SELECT d_week_seq + FROM date_dim + WHERE d_year = 1999 + 1 + AND d_moy = 12 + AND d_dom = 25) + GROUP BY i_brand_id, + i_class_id, + i_category_id + HAVING Sum(ss_quantity * ss_list_price) > (SELECT average_sales + FROM avg_sales)) this_year, + (SELECT 'store' channel, + i_brand_id, + i_class_id, + i_category_id, + Sum(ss_quantity * ss_list_price) sales, + Count(*) number_sales + FROM store_sales, + item, + date_dim + WHERE ss_item_sk IN (SELECT ss_item_sk + FROM cross_items) + AND ss_item_sk = i_item_sk + AND ss_sold_date_sk = d_date_sk + AND d_week_seq = (SELECT d_week_seq + FROM date_dim + WHERE d_year = 1999 + AND d_moy = 12 + AND d_dom = 25) + GROUP BY i_brand_id, + i_class_id, + i_category_id + HAVING Sum(ss_quantity * ss_list_price) > (SELECT average_sales + FROM avg_sales)) last_year +WHERE this_year.i_brand_id = last_year.i_brand_id + AND this_year.i_class_id = last_year.i_class_id + AND this_year.i_category_id = last_year.i_category_id +ORDER BY this_year.channel, + this_year.i_brand_id, + this_year.i_class_id, + this_year.i_category_id +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query15.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query15.sql new file mode 100644 index 000000000..207ddd719 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query15.sql @@ -0,0 +1,20 @@ +-- start query 15 in stream 0 using template query15.tpl +SELECT ca_zip, + Sum(cs_sales_price) +FROM catalog_sales, + customer, + customer_address, + date_dim +WHERE cs_bill_customer_sk = c_customer_sk + AND c_current_addr_sk = ca_address_sk + AND ( Substr(ca_zip, 1, 5) IN ( '85669', '86197', '88274', '83405', + '86475', '85392', '85460', '80348', + '81792' ) + OR ca_state IN ( 'CA', 'WA', 'GA' ) + OR cs_sales_price > 500 ) + AND cs_sold_date_sk = d_date_sk + AND d_qoy = 1 + AND d_year = 1998 +GROUP BY ca_zip +ORDER BY ca_zip +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query16.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query16.sql new file mode 100644 index 000000000..b19499577 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query16.sql @@ -0,0 +1,33 @@ +-- start query 16 in stream 0 using template query16.tpl +SELECT + Count(DISTINCT cs_order_number) AS `order count` , + Sum(cs_ext_ship_cost) AS `total shipping cost` , + Sum(cs_net_profit) AS `total net profit` +FROM catalog_sales cs1 , + date_dim , + customer_address , + call_center +WHERE d_date BETWEEN '2002-3-01' AND ( + Cast('2002-3-01' AS DATE) + INTERVAL '60' day) +AND cs1.cs_ship_date_sk = d_date_sk +AND cs1.cs_ship_addr_sk = ca_address_sk +AND ca_state = 'IA' +AND cs1.cs_call_center_sk = cc_call_center_sk +AND cc_county IN ('Williamson County', + 'Williamson County', + 'Williamson County', + 'Williamson County', + 'Williamson County' ) +AND EXISTS + ( + SELECT * + FROM catalog_sales cs2 + WHERE cs1.cs_order_number = cs2.cs_order_number + AND cs1.cs_warehouse_sk <> cs2.cs_warehouse_sk) +AND NOT EXISTS + ( + SELECT * + FROM catalog_returns cr1 + WHERE cs1.cs_order_number = cr1.cr_order_number) +ORDER BY count(DISTINCT cs_order_number) +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query17.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query17.sql new file mode 100644 index 000000000..da916d4a5 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query17.sql @@ -0,0 +1,56 @@ +-- start query 17 in stream 0 using template query17.tpl +SELECT i_item_id, + i_item_desc, + s_state, + Count(ss_quantity) AS + store_sales_quantitycount, + Avg(ss_quantity) AS + store_sales_quantityave, + Stddev_samp(ss_quantity) AS + store_sales_quantitystdev, + Stddev_samp(ss_quantity) / Avg(ss_quantity) AS + store_sales_quantitycov, + Count(sr_return_quantity) AS + store_returns_quantitycount, + Avg(sr_return_quantity) AS + store_returns_quantityave, + Stddev_samp(sr_return_quantity) AS + store_returns_quantitystdev, + Stddev_samp(sr_return_quantity) / Avg(sr_return_quantity) AS + store_returns_quantitycov, + Count(cs_quantity) AS + catalog_sales_quantitycount, + Avg(cs_quantity) AS + catalog_sales_quantityave, + Stddev_samp(cs_quantity) / Avg(cs_quantity) AS + catalog_sales_quantitystdev, + Stddev_samp(cs_quantity) / Avg(cs_quantity) AS + catalog_sales_quantitycov +FROM store_sales, + store_returns, + catalog_sales, + date_dim d1, + date_dim d2, + date_dim d3, + store, + item +WHERE d1.d_quarter_name = '1999Q1' + AND d1.d_date_sk = ss_sold_date_sk + AND i_item_sk = ss_item_sk + AND s_store_sk = ss_store_sk + AND ss_customer_sk = sr_customer_sk + AND ss_item_sk = sr_item_sk + AND ss_ticket_number = sr_ticket_number + AND sr_returned_date_sk = d2.d_date_sk + AND d2.d_quarter_name IN ( '1999Q1', '1999Q2', '1999Q3' ) + AND sr_customer_sk = cs_bill_customer_sk + AND sr_item_sk = cs_item_sk + AND cs_sold_date_sk = d3.d_date_sk + AND d3.d_quarter_name IN ( '1999Q1', '1999Q2', '1999Q3' ) +GROUP BY i_item_id, + i_item_desc, + s_state +ORDER BY i_item_id, + i_item_desc, + s_state +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query18.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query18.sql new file mode 100644 index 000000000..b59ce7ab0 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query18.sql @@ -0,0 +1,38 @@ +-- start query 18 in stream 0 using template query18.tpl +SELECT i_item_id, + ca_country, + ca_state, + ca_county, + Avg(Cast(cs_quantity AS DECIMAL(12, 2))) agg1, + Avg(Cast(cs_list_price AS DECIMAL(12, 2))) agg2, + Avg(Cast(cs_coupon_amt AS DECIMAL(12, 2))) agg3, + Avg(Cast(cs_sales_price AS DECIMAL(12, 2))) agg4, + Avg(Cast(cs_net_profit AS DECIMAL(12, 2))) agg5, + Avg(Cast(c_birth_year AS DECIMAL(12, 2))) agg6, + Avg(Cast(cd1.cd_dep_count AS DECIMAL(12, 2))) agg7 +FROM catalog_sales, + customer_demographics cd1, + customer_demographics cd2, + customer, + customer_address, + date_dim, + item +WHERE cs_sold_date_sk = d_date_sk + AND cs_item_sk = i_item_sk + AND cs_bill_cdemo_sk = cd1.cd_demo_sk + AND cs_bill_customer_sk = c_customer_sk + AND cd1.cd_gender = 'F' + AND cd1.cd_education_status = 'Secondary' + AND c_current_cdemo_sk = cd2.cd_demo_sk + AND c_current_addr_sk = ca_address_sk + AND c_birth_month IN ( 8, 4, 2, 5, + 11, 9 ) + AND d_year = 2001 + AND ca_state IN ( 'KS', 'IA', 'AL', 'UT', + 'VA', 'NC', 'TX' ) +GROUP BY rollup ( i_item_id, ca_country, ca_state, ca_county ) +ORDER BY ca_country, + ca_state, + ca_county, + i_item_id +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query19.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query19.sql new file mode 100644 index 000000000..c3039b2fd --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query19.sql @@ -0,0 +1,31 @@ +-- start query 19 in stream 0 using template query19.tpl +SELECT i_brand_id brand_id, + i_brand brand, + i_manufact_id, + i_manufact, + Sum(ss_ext_sales_price) ext_price +FROM date_dim, + store_sales, + item, + customer, + customer_address, + store +WHERE d_date_sk = ss_sold_date_sk + AND ss_item_sk = i_item_sk + AND i_manager_id = 38 + AND d_moy = 12 + AND d_year = 1998 + AND ss_customer_sk = c_customer_sk + AND c_current_addr_sk = ca_address_sk + AND Substr(ca_zip, 1, 5) <> Substr(s_zip, 1, 5) + AND ss_store_sk = s_store_sk +GROUP BY i_brand, + i_brand_id, + i_manufact_id, + i_manufact +ORDER BY ext_price DESC, + i_brand, + i_brand_id, + i_manufact_id, + i_manufact +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query2.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query2.sql new file mode 100644 index 000000000..c85af4246 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query2.sql @@ -0,0 +1,79 @@ +-- start query 2 in stream 0 using template query2.tpl +WITH wscs + AS (SELECT sold_date_sk, + sales_price + FROM (SELECT ws_sold_date_sk sold_date_sk, + ws_ext_sales_price sales_price + FROM web_sales) + UNION ALL + (SELECT cs_sold_date_sk sold_date_sk, + cs_ext_sales_price sales_price + FROM catalog_sales)), + wswscs + AS (SELECT d_week_seq, + Sum(CASE + WHEN ( d_day_name = 'Sunday' ) THEN sales_price + ELSE NULL + END) sun_sales, + Sum(CASE + WHEN ( d_day_name = 'Monday' ) THEN sales_price + ELSE NULL + END) mon_sales, + Sum(CASE + WHEN ( d_day_name = 'Tuesday' ) THEN sales_price + ELSE NULL + END) tue_sales, + Sum(CASE + WHEN ( d_day_name = 'Wednesday' ) THEN sales_price + ELSE NULL + END) wed_sales, + Sum(CASE + WHEN ( d_day_name = 'Thursday' ) THEN sales_price + ELSE NULL + END) thu_sales, + Sum(CASE + WHEN ( d_day_name = 'Friday' ) THEN sales_price + ELSE NULL + END) fri_sales, + Sum(CASE + WHEN ( d_day_name = 'Saturday' ) THEN sales_price + ELSE NULL + END) sat_sales + FROM wscs, + date_dim + WHERE d_date_sk = sold_date_sk + GROUP BY d_week_seq) +SELECT d_week_seq1, + Round(sun_sales1 / sun_sales2, 2), + Round(mon_sales1 / mon_sales2, 2), + Round(tue_sales1 / tue_sales2, 2), + Round(wed_sales1 / wed_sales2, 2), + Round(thu_sales1 / thu_sales2, 2), + Round(fri_sales1 / fri_sales2, 2), + Round(sat_sales1 / sat_sales2, 2) +FROM (SELECT wswscs.d_week_seq d_week_seq1, + sun_sales sun_sales1, + mon_sales mon_sales1, + tue_sales tue_sales1, + wed_sales wed_sales1, + thu_sales thu_sales1, + fri_sales fri_sales1, + sat_sales sat_sales1 + FROM wswscs, + date_dim + WHERE date_dim.d_week_seq = wswscs.d_week_seq + AND d_year = 1998) y, + (SELECT wswscs.d_week_seq d_week_seq2, + sun_sales sun_sales2, + mon_sales mon_sales2, + tue_sales tue_sales2, + wed_sales wed_sales2, + thu_sales thu_sales2, + fri_sales fri_sales2, + sat_sales sat_sales2 + FROM wswscs, + date_dim + WHERE date_dim.d_week_seq = wswscs.d_week_seq + AND d_year = 1998 + 1) z +WHERE d_week_seq1 = d_week_seq2 - 53 +ORDER BY d_week_seq1; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query20.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query20.sql new file mode 100644 index 000000000..3c73340ea --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query20.sql @@ -0,0 +1,30 @@ +-- start query 20 in stream 0 using template query20.tpl +SELECT + i_item_id , + i_item_desc , + i_category , + i_class , + i_current_price , + Sum(cs_ext_sales_price) AS itemrevenue , + Sum(cs_ext_sales_price)*100/Sum(Sum(cs_ext_sales_price)) OVER (partition BY i_class) AS revenueratio +FROM catalog_sales , + item , + date_dim +WHERE cs_item_sk = i_item_sk +AND i_category IN ('Children', + 'Women', + 'Electronics') +AND cs_sold_date_sk = d_date_sk +AND d_date BETWEEN Cast('2001-02-03' AS DATE) AND ( + Cast('2001-02-03' AS DATE) + INTERVAL '30' day) +GROUP BY i_item_id , + i_item_desc , + i_category , + i_class , + i_current_price +ORDER BY i_category , + i_class , + i_item_id , + i_item_desc , + revenueratio +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query21.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query21.sql new file mode 100644 index 000000000..1811226a8 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query21.sql @@ -0,0 +1,38 @@ +-- start query 21 in stream 0 using template query21.tpl +SELECT + * +FROM ( + SELECT w_warehouse_name , + i_item_id , + Sum( + CASE + WHEN ( + Cast(d_date AS DATE) < Cast ('2000-05-13' AS DATE)) THEN inv_quantity_on_hand + ELSE 0 + END) AS inv_before , + Sum( + CASE + WHEN ( + Cast(d_date AS DATE) >= Cast ('2000-05-13' AS DATE)) THEN inv_quantity_on_hand + ELSE 0 + END) AS inv_after + FROM inventory , + warehouse , + item , + date_dim + WHERE i_current_price BETWEEN 0.99 AND 1.49 + AND i_item_sk = inv_item_sk + AND inv_warehouse_sk = w_warehouse_sk + AND inv_date_sk = d_date_sk + AND d_date BETWEEN (Cast ('2000-05-13' AS DATE) - INTERVAL '30' day) AND ( + cast ('2000-05-13' AS date) + INTERVAL '30' day) + GROUP BY w_warehouse_name, + i_item_id) x +WHERE ( + CASE + WHEN inv_before > 0 THEN inv_after / inv_before + ELSE NULL + END) BETWEEN 2.0/3.0 AND 3.0/2.0 +ORDER BY w_warehouse_name , + i_item_id +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query22.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query22.sql new file mode 100644 index 000000000..707fc7c85 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query22.sql @@ -0,0 +1,21 @@ +-- start query 22 in stream 0 using template query22.tpl +SELECT i_product_name, + i_brand, + i_class, + i_category, + Avg(inv_quantity_on_hand) qoh +FROM inventory, + date_dim, + item, + warehouse +WHERE inv_date_sk = d_date_sk + AND inv_item_sk = i_item_sk + AND inv_warehouse_sk = w_warehouse_sk + AND d_month_seq BETWEEN 1205 AND 1205 + 11 +GROUP BY rollup( i_product_name, i_brand, i_class, i_category ) +ORDER BY qoh, + i_product_name, + i_brand, + i_class, + i_category +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query23.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query23.sql new file mode 100644 index 000000000..66ffc4412 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query23.sql @@ -0,0 +1,136 @@ +-- start query 23 in stream 0 using template query23.tpl +WITH frequent_ss_items + AS (SELECT Substr(i_item_desc, 1, 30) itemdesc, + i_item_sk item_sk, + d_date solddate, + Count(*) cnt + FROM store_sales, + date_dim, + item + WHERE ss_sold_date_sk = d_date_sk + AND ss_item_sk = i_item_sk + AND d_year IN ( 1998, 1998 + 1, 1998 + 2, 1998 + 3 ) + GROUP BY Substr(i_item_desc, 1, 30), + i_item_sk, + d_date + HAVING Count(*) > 4), + max_store_sales + AS (SELECT Max(csales) tpcds_cmax + FROM (SELECT c_customer_sk, + Sum(ss_quantity * ss_sales_price) csales + FROM store_sales, + customer, + date_dim + WHERE ss_customer_sk = c_customer_sk + AND ss_sold_date_sk = d_date_sk + AND d_year IN ( 1998, 1998 + 1, 1998 + 2, 1998 + 3 ) + GROUP BY c_customer_sk)), + best_ss_customer + AS (SELECT c_customer_sk, + Sum(ss_quantity * ss_sales_price) ssales + FROM store_sales, + customer + WHERE ss_customer_sk = c_customer_sk + GROUP BY c_customer_sk + HAVING Sum(ss_quantity * ss_sales_price) > + ( 95 / 100.0 ) * (SELECT * + FROM max_store_sales)) +SELECT Sum(sales) +FROM (SELECT cs_quantity * cs_list_price sales + FROM catalog_sales, + date_dim + WHERE d_year = 1998 + AND d_moy = 6 + AND cs_sold_date_sk = d_date_sk + AND cs_item_sk IN (SELECT item_sk + FROM frequent_ss_items) + AND cs_bill_customer_sk IN (SELECT c_customer_sk + FROM best_ss_customer) + UNION ALL + SELECT ws_quantity * ws_list_price sales + FROM web_sales, + date_dim + WHERE d_year = 1998 + AND d_moy = 6 + AND ws_sold_date_sk = d_date_sk + AND ws_item_sk IN (SELECT item_sk + FROM frequent_ss_items) + AND ws_bill_customer_sk IN (SELECT c_customer_sk + FROM best_ss_customer)) LIMIT 100; + +WITH frequent_ss_items + AS (SELECT Substr(i_item_desc, 1, 30) itemdesc, + i_item_sk item_sk, + d_date solddate, + Count(*) cnt + FROM store_sales, + date_dim, + item + WHERE ss_sold_date_sk = d_date_sk + AND ss_item_sk = i_item_sk + AND d_year IN ( 1998, 1998 + 1, 1998 + 2, 1998 + 3 ) + GROUP BY Substr(i_item_desc, 1, 30), + i_item_sk, + d_date + HAVING Count(*) > 4), + max_store_sales + AS (SELECT Max(csales) tpcds_cmax + FROM (SELECT c_customer_sk, + Sum(ss_quantity * ss_sales_price) csales + FROM store_sales, + customer, + date_dim + WHERE ss_customer_sk = c_customer_sk + AND ss_sold_date_sk = d_date_sk + AND d_year IN ( 1998, 1998 + 1, 1998 + 2, 1998 + 3 ) + GROUP BY c_customer_sk)), + best_ss_customer + AS (SELECT c_customer_sk, + Sum(ss_quantity * ss_sales_price) ssales + FROM store_sales, + customer + WHERE ss_customer_sk = c_customer_sk + GROUP BY c_customer_sk + HAVING Sum(ss_quantity * ss_sales_price) > + ( 95 / 100.0 ) * (SELECT * + FROM max_store_sales)) +SELECT c_last_name, + c_first_name, + sales +FROM (SELECT c_last_name, + c_first_name, + Sum(cs_quantity * cs_list_price) sales + FROM catalog_sales, + customer, + date_dim + WHERE d_year = 1998 + AND d_moy = 6 + AND cs_sold_date_sk = d_date_sk + AND cs_item_sk IN (SELECT item_sk + FROM frequent_ss_items) + AND cs_bill_customer_sk IN (SELECT c_customer_sk + FROM best_ss_customer) + AND cs_bill_customer_sk = c_customer_sk + GROUP BY c_last_name, + c_first_name + UNION ALL + SELECT c_last_name, + c_first_name, + Sum(ws_quantity * ws_list_price) sales + FROM web_sales, + customer, + date_dim + WHERE d_year = 1998 + AND d_moy = 6 + AND ws_sold_date_sk = d_date_sk + AND ws_item_sk IN (SELECT item_sk + FROM frequent_ss_items) + AND ws_bill_customer_sk IN (SELECT c_customer_sk + FROM best_ss_customer) + AND ws_bill_customer_sk = c_customer_sk + GROUP BY c_last_name, + c_first_name) +ORDER BY c_last_name, + c_first_name, + sales +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query24.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query24.sql new file mode 100644 index 000000000..8382ca81d --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query24.sql @@ -0,0 +1,96 @@ +-- start query 24 in stream 0 using template query24.tpl +WITH ssales + AS (SELECT c_last_name, + c_first_name, + s_store_name, + ca_state, + s_state, + i_color, + i_current_price, + i_manager_id, + i_units, + i_size, + Sum(ss_net_profit) netpaid + FROM store_sales, + store_returns, + store, + item, + customer, + customer_address + WHERE ss_ticket_number = sr_ticket_number + AND ss_item_sk = sr_item_sk + AND ss_customer_sk = c_customer_sk + AND ss_item_sk = i_item_sk + AND ss_store_sk = s_store_sk + AND c_birth_country = Upper(ca_country) + AND s_zip = ca_zip + AND s_market_id = 6 + GROUP BY c_last_name, + c_first_name, + s_store_name, + ca_state, + s_state, + i_color, + i_current_price, + i_manager_id, + i_units, + i_size) +SELECT c_last_name, + c_first_name, + s_store_name, + Sum(netpaid) paid +FROM ssales +WHERE i_color = 'papaya' +GROUP BY c_last_name, + c_first_name, + s_store_name +HAVING Sum(netpaid) > (SELECT 0.05 * Avg(netpaid) + FROM ssales); + +WITH ssales + AS (SELECT c_last_name, + c_first_name, + s_store_name, + ca_state, + s_state, + i_color, + i_current_price, + i_manager_id, + i_units, + i_size, + Sum(ss_net_profit) netpaid + FROM store_sales, + store_returns, + store, + item, + customer, + customer_address + WHERE ss_ticket_number = sr_ticket_number + AND ss_item_sk = sr_item_sk + AND ss_customer_sk = c_customer_sk + AND ss_item_sk = i_item_sk + AND ss_store_sk = s_store_sk + AND c_birth_country = Upper(ca_country) + AND s_zip = ca_zip + AND s_market_id = 6 + GROUP BY c_last_name, + c_first_name, + s_store_name, + ca_state, + s_state, + i_color, + i_current_price, + i_manager_id, + i_units, + i_size) +SELECT c_last_name, + c_first_name, + s_store_name, + Sum(netpaid) paid +FROM ssales +WHERE i_color = 'chartreuse' +GROUP BY c_last_name, + c_first_name, + s_store_name +HAVING Sum(netpaid) > (SELECT 0.05 * Avg(netpaid) + FROM ssales); diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query25.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query25.sql new file mode 100644 index 000000000..fe58702f6 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query25.sql @@ -0,0 +1,41 @@ +-- start query 25 in stream 0 using template query25.tpl +SELECT i_item_id, + i_item_desc, + s_store_id, + s_store_name, + Max(ss_net_profit) AS store_sales_profit, + Max(sr_net_loss) AS store_returns_loss, + Max(cs_net_profit) AS catalog_sales_profit +FROM store_sales, + store_returns, + catalog_sales, + date_dim d1, + date_dim d2, + date_dim d3, + store, + item +WHERE d1.d_moy = 4 + AND d1.d_year = 2001 + AND d1.d_date_sk = ss_sold_date_sk + AND i_item_sk = ss_item_sk + AND s_store_sk = ss_store_sk + AND ss_customer_sk = sr_customer_sk + AND ss_item_sk = sr_item_sk + AND ss_ticket_number = sr_ticket_number + AND sr_returned_date_sk = d2.d_date_sk + AND d2.d_moy BETWEEN 4 AND 10 + AND d2.d_year = 2001 + AND sr_customer_sk = cs_bill_customer_sk + AND sr_item_sk = cs_item_sk + AND cs_sold_date_sk = d3.d_date_sk + AND d3.d_moy BETWEEN 4 AND 10 + AND d3.d_year = 2001 +GROUP BY i_item_id, + i_item_desc, + s_store_id, + s_store_name +ORDER BY i_item_id, + i_item_desc, + s_store_id, + s_store_name +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query26.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query26.sql new file mode 100644 index 000000000..d4818a37b --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query26.sql @@ -0,0 +1,24 @@ +-- start query 26 in stream 0 using template query26.tpl +SELECT i_item_id, + Avg(cs_quantity) agg1, + Avg(cs_list_price) agg2, + Avg(cs_coupon_amt) agg3, + Avg(cs_sales_price) agg4 +FROM catalog_sales, + customer_demographics, + date_dim, + item, + promotion +WHERE cs_sold_date_sk = d_date_sk + AND cs_item_sk = i_item_sk + AND cs_bill_cdemo_sk = cd_demo_sk + AND cs_promo_sk = p_promo_sk + AND cd_gender = 'F' + AND cd_marital_status = 'W' + AND cd_education_status = 'Secondary' + AND ( p_channel_email = 'N' + OR p_channel_event = 'N' ) + AND d_year = 2000 +GROUP BY i_item_id +ORDER BY i_item_id +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query27.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query27.sql new file mode 100644 index 000000000..98fe056e5 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query27.sql @@ -0,0 +1,27 @@ +-- start query 27 in stream 0 using template query27.tpl +SELECT i_item_id, + s_state, + Grouping(s_state) g_state, + Avg(ss_quantity) agg1, + Avg(ss_list_price) agg2, + Avg(ss_coupon_amt) agg3, + Avg(ss_sales_price) agg4 +FROM store_sales, + customer_demographics, + date_dim, + store, + item +WHERE ss_sold_date_sk = d_date_sk + AND ss_item_sk = i_item_sk + AND ss_store_sk = s_store_sk + AND ss_cdemo_sk = cd_demo_sk + AND cd_gender = 'M' + AND cd_marital_status = 'D' + AND cd_education_status = 'College' + AND d_year = 2000 + AND s_state IN ( 'TN', 'TN', 'TN', 'TN', + 'TN', 'TN' ) +GROUP BY rollup ( i_item_id, s_state ) +ORDER BY i_item_id, + s_state +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query28.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query28.sql new file mode 100644 index 000000000..3aa74a9d9 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query28.sql @@ -0,0 +1,51 @@ +-- start query 28 in stream 0 using template query28.tpl +SELECT * +FROM (SELECT Avg(ss_list_price) B1_LP, + Count(ss_list_price) B1_CNT, + Count(DISTINCT ss_list_price) B1_CNTD + FROM store_sales + WHERE ss_quantity BETWEEN 0 AND 5 + AND ( ss_list_price BETWEEN 18 AND 18 + 10 + OR ss_coupon_amt BETWEEN 1939 AND 1939 + 1000 + OR ss_wholesale_cost BETWEEN 34 AND 34 + 20 )) B1, + (SELECT Avg(ss_list_price) B2_LP, + Count(ss_list_price) B2_CNT, + Count(DISTINCT ss_list_price) B2_CNTD + FROM store_sales + WHERE ss_quantity BETWEEN 6 AND 10 + AND ( ss_list_price BETWEEN 1 AND 1 + 10 + OR ss_coupon_amt BETWEEN 35 AND 35 + 1000 + OR ss_wholesale_cost BETWEEN 50 AND 50 + 20 )) B2, + (SELECT Avg(ss_list_price) B3_LP, + Count(ss_list_price) B3_CNT, + Count(DISTINCT ss_list_price) B3_CNTD + FROM store_sales + WHERE ss_quantity BETWEEN 11 AND 15 + AND ( ss_list_price BETWEEN 91 AND 91 + 10 + OR ss_coupon_amt BETWEEN 1412 AND 1412 + 1000 + OR ss_wholesale_cost BETWEEN 17 AND 17 + 20 )) B3, + (SELECT Avg(ss_list_price) B4_LP, + Count(ss_list_price) B4_CNT, + Count(DISTINCT ss_list_price) B4_CNTD + FROM store_sales + WHERE ss_quantity BETWEEN 16 AND 20 + AND ( ss_list_price BETWEEN 9 AND 9 + 10 + OR ss_coupon_amt BETWEEN 5270 AND 5270 + 1000 + OR ss_wholesale_cost BETWEEN 29 AND 29 + 20 )) B4, + (SELECT Avg(ss_list_price) B5_LP, + Count(ss_list_price) B5_CNT, + Count(DISTINCT ss_list_price) B5_CNTD + FROM store_sales + WHERE ss_quantity BETWEEN 21 AND 25 + AND ( ss_list_price BETWEEN 45 AND 45 + 10 + OR ss_coupon_amt BETWEEN 826 AND 826 + 1000 + OR ss_wholesale_cost BETWEEN 5 AND 5 + 20 )) B5, + (SELECT Avg(ss_list_price) B6_LP, + Count(ss_list_price) B6_CNT, + Count(DISTINCT ss_list_price) B6_CNTD + FROM store_sales + WHERE ss_quantity BETWEEN 26 AND 30 + AND ( ss_list_price BETWEEN 174 AND 174 + 10 + OR ss_coupon_amt BETWEEN 5548 AND 5548 + 1000 + OR ss_wholesale_cost BETWEEN 42 AND 42 + 20 )) B6 +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query29.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query29.sql new file mode 100644 index 000000000..b685e6179 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query29.sql @@ -0,0 +1,40 @@ +-- start query 29 in stream 0 using template query29.tpl +SELECT i_item_id, + i_item_desc, + s_store_id, + s_store_name, + Avg(ss_quantity) AS store_sales_quantity, + Avg(sr_return_quantity) AS store_returns_quantity, + Avg(cs_quantity) AS catalog_sales_quantity +FROM store_sales, + store_returns, + catalog_sales, + date_dim d1, + date_dim d2, + date_dim d3, + store, + item +WHERE d1.d_moy = 4 + AND d1.d_year = 1998 + AND d1.d_date_sk = ss_sold_date_sk + AND i_item_sk = ss_item_sk + AND s_store_sk = ss_store_sk + AND ss_customer_sk = sr_customer_sk + AND ss_item_sk = sr_item_sk + AND ss_ticket_number = sr_ticket_number + AND sr_returned_date_sk = d2.d_date_sk + AND d2.d_moy BETWEEN 4 AND 4 + 3 + AND d2.d_year = 1998 + AND sr_customer_sk = cs_bill_customer_sk + AND sr_item_sk = cs_item_sk + AND cs_sold_date_sk = d3.d_date_sk + AND d3.d_year IN ( 1998, 1998 + 1, 1998 + 2 ) +GROUP BY i_item_id, + i_item_desc, + s_store_id, + s_store_name +ORDER BY i_item_id, + i_item_desc, + s_store_id, + s_store_name +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query3.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query3.sql new file mode 100644 index 000000000..711b0fe7c --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query3.sql @@ -0,0 +1,19 @@ +-- start query 3 in stream 0 using template query3.tpl +SELECT dt.d_year, + item.i_brand_id brand_id, + item.i_brand brand, + Sum(ss_ext_discount_amt) sum_agg +FROM date_dim dt, + store_sales, + item +WHERE dt.d_date_sk = store_sales.ss_sold_date_sk + AND store_sales.ss_item_sk = item.i_item_sk + AND item.i_manufact_id = 427 + AND dt.d_moy = 11 +GROUP BY dt.d_year, + item.i_brand, + item.i_brand_id +ORDER BY dt.d_year, + sum_agg DESC, + brand_id +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query30.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query30.sql new file mode 100644 index 000000000..4b0498f72 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query30.sql @@ -0,0 +1,49 @@ +-- start query 30 in stream 0 using template query30.tpl +WITH customer_total_return + AS (SELECT wr_returning_customer_sk AS ctr_customer_sk, + ca_state AS ctr_state, + Sum(wr_return_amt) AS ctr_total_return + FROM web_returns, + date_dim, + customer_address + WHERE wr_returned_date_sk = d_date_sk + AND d_year = 2000 + AND wr_returning_addr_sk = ca_address_sk + GROUP BY wr_returning_customer_sk, + ca_state) +SELECT c_customer_id, + c_salutation, + c_first_name, + c_last_name, + c_preferred_cust_flag, + c_birth_day, + c_birth_month, + c_birth_year, + c_birth_country, + c_login, + c_email_address, + c_last_review_date, + ctr_total_return +FROM customer_total_return ctr1, + customer_address, + customer +WHERE ctr1.ctr_total_return > (SELECT Avg(ctr_total_return) * 1.2 + FROM customer_total_return ctr2 + WHERE ctr1.ctr_state = ctr2.ctr_state) + AND ca_address_sk = c_current_addr_sk + AND ca_state = 'IN' + AND ctr1.ctr_customer_sk = c_customer_sk +ORDER BY c_customer_id, + c_salutation, + c_first_name, + c_last_name, + c_preferred_cust_flag, + c_birth_day, + c_birth_month, + c_birth_year, + c_birth_country, + c_login, + c_email_address, + c_last_review_date, + ctr_total_return +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query31.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query31.sql new file mode 100644 index 000000000..8ab3ffb41 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query31.sql @@ -0,0 +1,73 @@ +-- start query 31 in stream 0 using template query31.tpl +WITH ss + AS (SELECT ca_county, + d_qoy, + d_year, + Sum(ss_ext_sales_price) AS store_sales + FROM store_sales, + date_dim, + customer_address + WHERE ss_sold_date_sk = d_date_sk + AND ss_addr_sk = ca_address_sk + GROUP BY ca_county, + d_qoy, + d_year), + ws + AS (SELECT ca_county, + d_qoy, + d_year, + Sum(ws_ext_sales_price) AS web_sales + FROM web_sales, + date_dim, + customer_address + WHERE ws_sold_date_sk = d_date_sk + AND ws_bill_addr_sk = ca_address_sk + GROUP BY ca_county, + d_qoy, + d_year) +SELECT ss1.ca_county, + ss1.d_year, + ws2.web_sales / ws1.web_sales web_q1_q2_increase, + ss2.store_sales / ss1.store_sales store_q1_q2_increase, + ws3.web_sales / ws2.web_sales web_q2_q3_increase, + ss3.store_sales / ss2.store_sales store_q2_q3_increase +FROM ss ss1, + ss ss2, + ss ss3, + ws ws1, + ws ws2, + ws ws3 +WHERE ss1.d_qoy = 1 + AND ss1.d_year = 2001 + AND ss1.ca_county = ss2.ca_county + AND ss2.d_qoy = 2 + AND ss2.d_year = 2001 + AND ss2.ca_county = ss3.ca_county + AND ss3.d_qoy = 3 + AND ss3.d_year = 2001 + AND ss1.ca_county = ws1.ca_county + AND ws1.d_qoy = 1 + AND ws1.d_year = 2001 + AND ws1.ca_county = ws2.ca_county + AND ws2.d_qoy = 2 + AND ws2.d_year = 2001 + AND ws1.ca_county = ws3.ca_county + AND ws3.d_qoy = 3 + AND ws3.d_year = 2001 + AND CASE + WHEN ws1.web_sales > 0 THEN ws2.web_sales / ws1.web_sales + ELSE NULL + END > CASE + WHEN ss1.store_sales > 0 THEN + ss2.store_sales / ss1.store_sales + ELSE NULL + END + AND CASE + WHEN ws2.web_sales > 0 THEN ws3.web_sales / ws2.web_sales + ELSE NULL + END > CASE + WHEN ss2.store_sales > 0 THEN + ss3.store_sales / ss2.store_sales + ELSE NULL + END +ORDER BY ss1.d_year; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query32.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query32.sql new file mode 100644 index 000000000..560c1e7cd --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query32.sql @@ -0,0 +1,21 @@ +-- start query 32 in stream 0 using template query32.tpl +SELECT + Sum(cs_ext_discount_amt) AS `excess discount amount` +FROM catalog_sales , + item , + date_dim +WHERE i_manufact_id = 610 +AND i_item_sk = cs_item_sk +AND d_date BETWEEN '2001-03-04' AND ( + Cast('2001-03-04' AS DATE) + INTERVAL '90' day) +AND d_date_sk = cs_sold_date_sk +AND cs_ext_discount_amt > + ( + SELECT 1.3 * avg(cs_ext_discount_amt) + FROM catalog_sales , + date_dim + WHERE cs_item_sk = i_item_sk + AND d_date BETWEEN '2001-03-04' AND ( + cast('2001-03-04' AS date) + INTERVAL '90' day) + AND d_date_sk = cs_sold_date_sk ) +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query33.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query33.sql new file mode 100644 index 000000000..c4161054a --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query33.sql @@ -0,0 +1,65 @@ +-- start query 33 in stream 0 using template query33.tpl +WITH ss + AS (SELECT i_manufact_id, + Sum(ss_ext_sales_price) total_sales + FROM store_sales, + date_dim, + customer_address, + item + WHERE i_manufact_id IN (SELECT i_manufact_id + FROM item + WHERE i_category IN ( 'Books' )) + AND ss_item_sk = i_item_sk + AND ss_sold_date_sk = d_date_sk + AND d_year = 1999 + AND d_moy = 3 + AND ss_addr_sk = ca_address_sk + AND ca_gmt_offset = -5 + GROUP BY i_manufact_id), + cs + AS (SELECT i_manufact_id, + Sum(cs_ext_sales_price) total_sales + FROM catalog_sales, + date_dim, + customer_address, + item + WHERE i_manufact_id IN (SELECT i_manufact_id + FROM item + WHERE i_category IN ( 'Books' )) + AND cs_item_sk = i_item_sk + AND cs_sold_date_sk = d_date_sk + AND d_year = 1999 + AND d_moy = 3 + AND cs_bill_addr_sk = ca_address_sk + AND ca_gmt_offset = -5 + GROUP BY i_manufact_id), + ws + AS (SELECT i_manufact_id, + Sum(ws_ext_sales_price) total_sales + FROM web_sales, + date_dim, + customer_address, + item + WHERE i_manufact_id IN (SELECT i_manufact_id + FROM item + WHERE i_category IN ( 'Books' )) + AND ws_item_sk = i_item_sk + AND ws_sold_date_sk = d_date_sk + AND d_year = 1999 + AND d_moy = 3 + AND ws_bill_addr_sk = ca_address_sk + AND ca_gmt_offset = -5 + GROUP BY i_manufact_id) +SELECT i_manufact_id, + Sum(total_sales) total_sales +FROM (SELECT * + FROM ss + UNION ALL + SELECT * + FROM cs + UNION ALL + SELECT * + FROM ws) tmp1 +GROUP BY i_manufact_id +ORDER BY total_sales +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query34.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query34.sql new file mode 100644 index 000000000..613734ffa --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query34.sql @@ -0,0 +1,46 @@ +-- start query 34 in stream 0 using template query34.tpl +SELECT c_last_name, + c_first_name, + c_salutation, + c_preferred_cust_flag, + ss_ticket_number, + cnt +FROM (SELECT ss_ticket_number, + ss_customer_sk, + Count(*) cnt + FROM store_sales, + date_dim, + store, + household_demographics + WHERE store_sales.ss_sold_date_sk = date_dim.d_date_sk + AND store_sales.ss_store_sk = store.s_store_sk + AND store_sales.ss_hdemo_sk = household_demographics.hd_demo_sk + AND ( date_dim.d_dom BETWEEN 1 AND 3 + OR date_dim.d_dom BETWEEN 25 AND 28 ) + AND ( household_demographics.hd_buy_potential = '>10000' + OR household_demographics.hd_buy_potential = 'unknown' ) + AND household_demographics.hd_vehicle_count > 0 + AND ( CASE + WHEN household_demographics.hd_vehicle_count > 0 THEN + household_demographics.hd_dep_count / + household_demographics.hd_vehicle_count + ELSE NULL + END ) > 1.2 + AND date_dim.d_year IN ( 1999, 1999 + 1, 1999 + 2 ) + AND store.s_county IN ( 'Williamson County', 'Williamson County', + 'Williamson County', + 'Williamson County' + , + 'Williamson County', 'Williamson County', + 'Williamson County', + 'Williamson County' + ) + GROUP BY ss_ticket_number, + ss_customer_sk) dn, + customer +WHERE ss_customer_sk = c_customer_sk + AND cnt BETWEEN 15 AND 20 +ORDER BY c_last_name, + c_first_name, + c_salutation, + c_preferred_cust_flag DESC; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query35.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query35.sql new file mode 100644 index 000000000..d5912a411 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query35.sql @@ -0,0 +1,58 @@ +-- start query 35 in stream 0 using template query35.tpl +SELECT ca_state, + cd_gender, + cd_marital_status, + cd_dep_count, + Count(*) cnt1, + Stddev_samp(cd_dep_count), + Avg(cd_dep_count), + Max(cd_dep_count), + cd_dep_employed_count, + Count(*) cnt2, + Stddev_samp(cd_dep_employed_count), + Avg(cd_dep_employed_count), + Max(cd_dep_employed_count), + cd_dep_college_count, + Count(*) cnt3, + Stddev_samp(cd_dep_college_count), + Avg(cd_dep_college_count), + Max(cd_dep_college_count) +FROM customer c, + customer_address ca, + customer_demographics +WHERE c.c_current_addr_sk = ca.ca_address_sk + AND cd_demo_sk = c.c_current_cdemo_sk + AND EXISTS (SELECT * + FROM store_sales, + date_dim + WHERE c.c_customer_sk = ss_customer_sk + AND ss_sold_date_sk = d_date_sk + AND d_year = 2001 + AND d_qoy < 4) + AND ( EXISTS (SELECT * + FROM web_sales, + date_dim + WHERE c.c_customer_sk = ws_bill_customer_sk + AND ws_sold_date_sk = d_date_sk + AND d_year = 2001 + AND d_qoy < 4) + OR EXISTS (SELECT * + FROM catalog_sales, + date_dim + WHERE c.c_customer_sk = cs_ship_customer_sk + AND cs_sold_date_sk = d_date_sk + AND d_year = 2001 + AND d_qoy < 4) ) +GROUP BY ca_state, + cd_gender, + cd_marital_status, + cd_dep_count, + cd_dep_employed_count, + cd_dep_college_count +ORDER BY ca_state, + cd_gender, + cd_marital_status, + cd_dep_count, + cd_dep_employed_count, + cd_dep_college_count +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query36.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query36.sql new file mode 100644 index 000000000..21d69fbbb --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query36.sql @@ -0,0 +1,31 @@ +-- start query 36 in stream 0 using template query36.tpl +SELECT Sum(ss_net_profit) / Sum(ss_ext_sales_price) AS + gross_margin, + i_category, + i_class, + Grouping(i_category) + Grouping(i_class) AS + lochierarchy, + Rank() + OVER ( + partition BY Grouping(i_category)+Grouping(i_class), CASE + WHEN Grouping( + i_class) = 0 THEN i_category END + ORDER BY Sum(ss_net_profit)/Sum(ss_ext_sales_price) ASC) AS + rank_within_parent +FROM store_sales, + date_dim d1, + item, + store +WHERE d1.d_year = 2000 + AND d1.d_date_sk = ss_sold_date_sk + AND i_item_sk = ss_item_sk + AND s_store_sk = ss_store_sk + AND s_state IN ( 'TN', 'TN', 'TN', 'TN', + 'TN', 'TN', 'TN', 'TN' ) +GROUP BY rollup( i_category, i_class ) +ORDER BY lochierarchy DESC, + CASE + WHEN lochierarchy = 0 THEN i_category + END, + rank_within_parent +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query37.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query37.sql new file mode 100644 index 000000000..52cd153a9 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query37.sql @@ -0,0 +1,22 @@ +-- start query 37 in stream 0 using template query37.tpl +SELECT + i_item_id , + i_item_desc , + i_current_price +FROM item, + inventory, + date_dim, + catalog_sales +WHERE i_current_price BETWEEN 20 AND 20 + 30 +AND inv_item_sk = i_item_sk +AND d_date_sk=inv_date_sk +AND d_date BETWEEN Cast('1999-03-06' AS DATE) AND ( + Cast('1999-03-06' AS DATE) + INTERVAL '60' day) +AND i_manufact_id IN (843,815,850,840) +AND inv_quantity_on_hand BETWEEN 100 AND 500 +AND cs_item_sk = i_item_sk +GROUP BY i_item_id, + i_item_desc, + i_current_price +ORDER BY i_item_id +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query38.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query38.sql new file mode 100644 index 000000000..546068717 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query38.sql @@ -0,0 +1,32 @@ +-- start query 38 in stream 0 using template query38.tpl +SELECT Count(*) +FROM (SELECT DISTINCT c_last_name, + c_first_name, + d_date + FROM store_sales, + date_dim, + customer + WHERE store_sales.ss_sold_date_sk = date_dim.d_date_sk + AND store_sales.ss_customer_sk = customer.c_customer_sk + AND d_month_seq BETWEEN 1188 AND 1188 + 11 + INTERSECT + SELECT DISTINCT c_last_name, + c_first_name, + d_date + FROM catalog_sales, + date_dim, + customer + WHERE catalog_sales.cs_sold_date_sk = date_dim.d_date_sk + AND catalog_sales.cs_bill_customer_sk = customer.c_customer_sk + AND d_month_seq BETWEEN 1188 AND 1188 + 11 + INTERSECT + SELECT DISTINCT c_last_name, + c_first_name, + d_date + FROM web_sales, + date_dim, + customer + WHERE web_sales.ws_sold_date_sk = date_dim.d_date_sk + AND web_sales.ws_bill_customer_sk = customer.c_customer_sk + AND d_month_seq BETWEEN 1188 AND 1188 + 11) hot_cust +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query39.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query39.sql new file mode 100644 index 000000000..4abb67d30 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query39.sql @@ -0,0 +1,117 @@ +-- start query 39 in stream 0 using template query39.tpl +WITH inv + AS (SELECT w_warehouse_name, + w_warehouse_sk, + i_item_sk, + d_moy, + stdev, + mean, + CASE mean + WHEN 0 THEN NULL + ELSE stdev / mean + END cov + FROM (SELECT w_warehouse_name, + w_warehouse_sk, + i_item_sk, + d_moy, + Stddev_samp(inv_quantity_on_hand) stdev, + Avg(inv_quantity_on_hand) mean + FROM inventory, + item, + warehouse, + date_dim + WHERE inv_item_sk = i_item_sk + AND inv_warehouse_sk = w_warehouse_sk + AND inv_date_sk = d_date_sk + AND d_year = 2002 + GROUP BY w_warehouse_name, + w_warehouse_sk, + i_item_sk, + d_moy) foo + WHERE CASE mean + WHEN 0 THEN 0 + ELSE stdev / mean + END > 1) +SELECT inv1.w_warehouse_sk, + inv1.i_item_sk, + inv1.d_moy, + inv1.mean, + inv1.cov, + inv2.w_warehouse_sk, + inv2.i_item_sk, + inv2.d_moy, + inv2.mean, + inv2.cov +FROM inv inv1, + inv inv2 +WHERE inv1.i_item_sk = inv2.i_item_sk + AND inv1.w_warehouse_sk = inv2.w_warehouse_sk + AND inv1.d_moy = 1 + AND inv2.d_moy = 1 + 1 +ORDER BY inv1.w_warehouse_sk, + inv1.i_item_sk, + inv1.d_moy, + inv1.mean, + inv1.cov, + inv2.d_moy, + inv2.mean, + inv2.cov; + +WITH inv + AS (SELECT w_warehouse_name, + w_warehouse_sk, + i_item_sk, + d_moy, + stdev, + mean, + CASE mean + WHEN 0 THEN NULL + ELSE stdev / mean + END cov + FROM (SELECT w_warehouse_name, + w_warehouse_sk, + i_item_sk, + d_moy, + Stddev_samp(inv_quantity_on_hand) stdev, + Avg(inv_quantity_on_hand) mean + FROM inventory, + item, + warehouse, + date_dim + WHERE inv_item_sk = i_item_sk + AND inv_warehouse_sk = w_warehouse_sk + AND inv_date_sk = d_date_sk + AND d_year = 2002 + GROUP BY w_warehouse_name, + w_warehouse_sk, + i_item_sk, + d_moy) foo + WHERE CASE mean + WHEN 0 THEN 0 + ELSE stdev / mean + END > 1) +SELECT inv1.w_warehouse_sk, + inv1.i_item_sk, + inv1.d_moy, + inv1.mean, + inv1.cov, + inv2.w_warehouse_sk, + inv2.i_item_sk, + inv2.d_moy, + inv2.mean, + inv2.cov +FROM inv inv1, + inv inv2 +WHERE inv1.i_item_sk = inv2.i_item_sk + AND inv1.w_warehouse_sk = inv2.w_warehouse_sk + AND inv1.d_moy = 1 + AND inv2.d_moy = 1 + 1 + AND inv1.cov > 1.5 +ORDER BY inv1.w_warehouse_sk, + inv1.i_item_sk, + inv1.d_moy, + inv1.mean, + inv1.cov, + inv2.d_moy, + inv2.mean, + inv2.cov; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query4.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query4.sql new file mode 100644 index 000000000..281b2d5d2 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query4.sql @@ -0,0 +1,152 @@ +-- start query 4 in stream 0 using template query4.tpl +WITH year_total + AS (SELECT c_customer_id customer_id, + c_first_name customer_first_name, + c_last_name customer_last_name, + c_preferred_cust_flag customer_preferred_cust_flag + , + c_birth_country + customer_birth_country, + c_login customer_login, + c_email_address customer_email_address, + d_year dyear, + Sum(( ( ss_ext_list_price - ss_ext_wholesale_cost + - ss_ext_discount_amt + ) + + + ss_ext_sales_price ) / 2) year_total, + 's' sale_type + FROM customer, + store_sales, + date_dim + WHERE c_customer_sk = ss_customer_sk + AND ss_sold_date_sk = d_date_sk + GROUP BY c_customer_id, + c_first_name, + c_last_name, + c_preferred_cust_flag, + c_birth_country, + c_login, + c_email_address, + d_year + UNION ALL + SELECT c_customer_id customer_id, + c_first_name customer_first_name, + c_last_name customer_last_name, + c_preferred_cust_flag + customer_preferred_cust_flag, + c_birth_country customer_birth_country + , + c_login + customer_login, + c_email_address customer_email_address + , + d_year dyear + , + Sum(( ( ( cs_ext_list_price + - cs_ext_wholesale_cost + - cs_ext_discount_amt + ) + + cs_ext_sales_price ) / 2 )) year_total, + 'c' sale_type + FROM customer, + catalog_sales, + date_dim + WHERE c_customer_sk = cs_bill_customer_sk + AND cs_sold_date_sk = d_date_sk + GROUP BY c_customer_id, + c_first_name, + c_last_name, + c_preferred_cust_flag, + c_birth_country, + c_login, + c_email_address, + d_year + UNION ALL + SELECT c_customer_id customer_id, + c_first_name customer_first_name, + c_last_name customer_last_name, + c_preferred_cust_flag + customer_preferred_cust_flag, + c_birth_country customer_birth_country + , + c_login + customer_login, + c_email_address customer_email_address + , + d_year dyear + , + Sum(( ( ( ws_ext_list_price + - ws_ext_wholesale_cost + - ws_ext_discount_amt + ) + + ws_ext_sales_price ) / 2 )) year_total, + 'w' sale_type + FROM customer, + web_sales, + date_dim + WHERE c_customer_sk = ws_bill_customer_sk + AND ws_sold_date_sk = d_date_sk + GROUP BY c_customer_id, + c_first_name, + c_last_name, + c_preferred_cust_flag, + c_birth_country, + c_login, + c_email_address, + d_year) +SELECT t_s_secyear.customer_id, + t_s_secyear.customer_first_name, + t_s_secyear.customer_last_name, + t_s_secyear.customer_preferred_cust_flag +FROM year_total t_s_firstyear, + year_total t_s_secyear, + year_total t_c_firstyear, + year_total t_c_secyear, + year_total t_w_firstyear, + year_total t_w_secyear +WHERE t_s_secyear.customer_id = t_s_firstyear.customer_id + AND t_s_firstyear.customer_id = t_c_secyear.customer_id + AND t_s_firstyear.customer_id = t_c_firstyear.customer_id + AND t_s_firstyear.customer_id = t_w_firstyear.customer_id + AND t_s_firstyear.customer_id = t_w_secyear.customer_id + AND t_s_firstyear.sale_type = 's' + AND t_c_firstyear.sale_type = 'c' + AND t_w_firstyear.sale_type = 'w' + AND t_s_secyear.sale_type = 's' + AND t_c_secyear.sale_type = 'c' + AND t_w_secyear.sale_type = 'w' + AND t_s_firstyear.dyear = 2001 + AND t_s_secyear.dyear = 2001 + 1 + AND t_c_firstyear.dyear = 2001 + AND t_c_secyear.dyear = 2001 + 1 + AND t_w_firstyear.dyear = 2001 + AND t_w_secyear.dyear = 2001 + 1 + AND t_s_firstyear.year_total > 0 + AND t_c_firstyear.year_total > 0 + AND t_w_firstyear.year_total > 0 + AND CASE + WHEN t_c_firstyear.year_total > 0 THEN t_c_secyear.year_total / + t_c_firstyear.year_total + ELSE NULL + END > CASE + WHEN t_s_firstyear.year_total > 0 THEN + t_s_secyear.year_total / + t_s_firstyear.year_total + ELSE NULL + END + AND CASE + WHEN t_c_firstyear.year_total > 0 THEN t_c_secyear.year_total / + t_c_firstyear.year_total + ELSE NULL + END > CASE + WHEN t_w_firstyear.year_total > 0 THEN + t_w_secyear.year_total / + t_w_firstyear.year_total + ELSE NULL + END +ORDER BY t_s_secyear.customer_id, + t_s_secyear.customer_first_name, + t_s_secyear.customer_last_name, + t_s_secyear.customer_preferred_cust_flag +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query40.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query40.sql new file mode 100644 index 000000000..f7f84b873 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query40.sql @@ -0,0 +1,35 @@ +-- start query 40 in stream 0 using template query40.tpl +SELECT + w_state , + i_item_id , + Sum( + CASE + WHEN ( + Cast(d_date AS DATE) < Cast ('2002-06-01' AS DATE)) THEN cs_sales_price - COALESCE(cr_refunded_cash,0) + ELSE 0 + END) AS sales_before , + Sum( + CASE + WHEN ( + Cast(d_date AS DATE) >= Cast ('2002-06-01' AS DATE)) THEN cs_sales_price - COALESCE(cr_refunded_cash,0) + ELSE 0 + END) AS sales_after +FROM catalog_sales +LEFT OUTER JOIN catalog_returns +ON ( + cs_order_number = cr_order_number + AND cs_item_sk = cr_item_sk) , + warehouse , + item , + date_dim +WHERE i_current_price BETWEEN 0.99 AND 1.49 +AND i_item_sk = cs_item_sk +AND cs_warehouse_sk = w_warehouse_sk +AND cs_sold_date_sk = d_date_sk +AND d_date BETWEEN (Cast ('2002-06-01' AS DATE) - INTERVAL '30' day) AND ( + cast ('2002-06-01' AS date) + INTERVAL '30' day) +GROUP BY w_state, + i_item_id +ORDER BY w_state, + i_item_id +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query41.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query41.sql new file mode 100644 index 000000000..467ed35ae --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query41.sql @@ -0,0 +1,66 @@ +-- start query 41 in stream 0 using template query41.tpl +SELECT Distinct(i_product_name) +FROM item i1 +WHERE i_manufact_id BETWEEN 765 AND 765 + 40 + AND (SELECT Count(*) AS item_cnt + FROM item + WHERE ( i_manufact = i1.i_manufact + AND ( ( i_category = 'Women' + AND ( i_color = 'dim' + OR i_color = 'green' ) + AND ( i_units = 'Gross' + OR i_units = 'Dozen' ) + AND ( i_size = 'economy' + OR i_size = 'petite' ) ) + OR ( i_category = 'Women' + AND ( i_color = 'navajo' + OR i_color = 'aquamarine' ) + AND ( i_units = 'Case' + OR i_units = 'Unknown' ) + AND ( i_size = 'large' + OR i_size = 'N/A' ) ) + OR ( i_category = 'Men' + AND ( i_color = 'indian' + OR i_color = 'dark' ) + AND ( i_units = 'Oz' + OR i_units = 'Lb' ) + AND ( i_size = 'extra large' + OR i_size = 'small' ) ) + OR ( i_category = 'Men' + AND ( i_color = 'peach' + OR i_color = 'purple' ) + AND ( i_units = 'Tbl' + OR i_units = 'Bunch' ) + AND ( i_size = 'economy' + OR i_size = 'petite' ) ) ) ) + OR ( i_manufact = i1.i_manufact + AND ( ( i_category = 'Women' + AND ( i_color = 'orchid' + OR i_color = 'peru' ) + AND ( i_units = 'Carton' + OR i_units = 'Cup' ) + AND ( i_size = 'economy' + OR i_size = 'petite' ) ) + OR ( i_category = 'Women' + AND ( i_color = 'violet' + OR i_color = 'papaya' ) + AND ( i_units = 'Ounce' + OR i_units = 'Box' ) + AND ( i_size = 'large' + OR i_size = 'N/A' ) ) + OR ( i_category = 'Men' + AND ( i_color = 'drab' + OR i_color = 'grey' ) + AND ( i_units = 'Each' + OR i_units = 'N/A' ) + AND ( i_size = 'extra large' + OR i_size = 'small' ) ) + OR ( i_category = 'Men' + AND ( i_color = 'chocolate' + OR i_color = 'antique' ) + AND ( i_units = 'Dram' + OR i_units = 'Gram' ) + AND ( i_size = 'economy' + OR i_size = 'petite' ) ) ) )) > 0 +ORDER BY i_product_name +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query42.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query42.sql new file mode 100644 index 000000000..706d3c6f1 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query42.sql @@ -0,0 +1,21 @@ +-- start query 42 in stream 0 using template query42.tpl +SELECT dt.d_year, + item.i_category_id, + item.i_category, + Sum(ss_ext_sales_price) +FROM date_dim dt, + store_sales, + item +WHERE dt.d_date_sk = store_sales.ss_sold_date_sk + AND store_sales.ss_item_sk = item.i_item_sk + AND item.i_manager_id = 1 + AND dt.d_moy = 12 + AND dt.d_year = 2000 +GROUP BY dt.d_year, + item.i_category_id, + item.i_category +ORDER BY Sum(ss_ext_sales_price) DESC, + dt.d_year, + item.i_category_id, + item.i_category +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query43.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query43.sql new file mode 100644 index 000000000..e7624a19c --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query43.sql @@ -0,0 +1,50 @@ +-- start query 43 in stream 0 using template query43.tpl +SELECT s_store_name, + s_store_id, + Sum(CASE + WHEN ( d_day_name = 'Sunday' ) THEN ss_sales_price + ELSE NULL + END) sun_sales, + Sum(CASE + WHEN ( d_day_name = 'Monday' ) THEN ss_sales_price + ELSE NULL + END) mon_sales, + Sum(CASE + WHEN ( d_day_name = 'Tuesday' ) THEN ss_sales_price + ELSE NULL + END) tue_sales, + Sum(CASE + WHEN ( d_day_name = 'Wednesday' ) THEN ss_sales_price + ELSE NULL + END) wed_sales, + Sum(CASE + WHEN ( d_day_name = 'Thursday' ) THEN ss_sales_price + ELSE NULL + END) thu_sales, + Sum(CASE + WHEN ( d_day_name = 'Friday' ) THEN ss_sales_price + ELSE NULL + END) fri_sales, + Sum(CASE + WHEN ( d_day_name = 'Saturday' ) THEN ss_sales_price + ELSE NULL + END) sat_sales +FROM date_dim, + store_sales, + store +WHERE d_date_sk = ss_sold_date_sk + AND s_store_sk = ss_store_sk + AND s_gmt_offset = -5 + AND d_year = 2002 +GROUP BY s_store_name, + s_store_id +ORDER BY s_store_name, + s_store_id, + sun_sales, + mon_sales, + tue_sales, + wed_sales, + thu_sales, + fri_sales, + sat_sales +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query44.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query44.sql new file mode 100644 index 000000000..ba869d3ce --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query44.sql @@ -0,0 +1,51 @@ +-- start query 44 in stream 0 using template query44.tpl +SELECT asceding.rnk, + i1.i_product_name best_performing, + i2.i_product_name worst_performing +FROM (SELECT * + FROM (SELECT item_sk, + Rank() + OVER ( + ORDER BY rank_col ASC) rnk + FROM (SELECT ss_item_sk item_sk, + Avg(ss_net_profit) rank_col + FROM store_sales ss1 + WHERE ss_store_sk = 4 + GROUP BY ss_item_sk + HAVING Avg(ss_net_profit) > 0.9 * + (SELECT Avg(ss_net_profit) + rank_col + FROM store_sales + WHERE ss_store_sk = 4 + AND ss_cdemo_sk IS + NULL + GROUP BY ss_store_sk))V1) + V11 + WHERE rnk < 11) asceding, + (SELECT * + FROM (SELECT item_sk, + Rank() + OVER ( + ORDER BY rank_col DESC) rnk + FROM (SELECT ss_item_sk item_sk, + Avg(ss_net_profit) rank_col + FROM store_sales ss1 + WHERE ss_store_sk = 4 + GROUP BY ss_item_sk + HAVING Avg(ss_net_profit) > 0.9 * + (SELECT Avg(ss_net_profit) + rank_col + FROM store_sales + WHERE ss_store_sk = 4 + AND ss_cdemo_sk IS + NULL + GROUP BY ss_store_sk))V2) + V21 + WHERE rnk < 11) descending, + item i1, + item i2 +WHERE asceding.rnk = descending.rnk + AND i1.i_item_sk = asceding.item_sk + AND i2.i_item_sk = descending.item_sk +ORDER BY asceding.rnk +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query45.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query45.sql new file mode 100644 index 000000000..e8d35c1c0 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query45.sql @@ -0,0 +1,28 @@ +-- start query 45 in stream 0 using template query45.tpl +SELECT ca_zip, + ca_state, + Sum(ws_sales_price) +FROM web_sales, + customer, + customer_address, + date_dim, + item +WHERE ws_bill_customer_sk = c_customer_sk + AND c_current_addr_sk = ca_address_sk + AND ws_item_sk = i_item_sk + AND ( Substr(ca_zip, 1, 5) IN ( '85669', '86197', '88274', '83405', + '86475', '85392', '85460', '80348', + '81792' ) + OR i_item_id IN (SELECT i_item_id + FROM item + WHERE i_item_sk IN ( 2, 3, 5, 7, + 11, 13, 17, 19, + 23, 29 )) ) + AND ws_sold_date_sk = d_date_sk + AND d_qoy = 1 + AND d_year = 2000 +GROUP BY ca_zip, + ca_state +ORDER BY ca_zip, + ca_state +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query46.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query46.sql new file mode 100644 index 000000000..b88b36400 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query46.sql @@ -0,0 +1,44 @@ +-- start query 46 in stream 0 using template query46.tpl +SELECT c_last_name, + c_first_name, + ca_city, + bought_city, + ss_ticket_number, + amt, + profit +FROM (SELECT ss_ticket_number, + ss_customer_sk, + ca_city bought_city, + Sum(ss_coupon_amt) amt, + Sum(ss_net_profit) profit + FROM store_sales, + date_dim, + store, + household_demographics, + customer_address + WHERE store_sales.ss_sold_date_sk = date_dim.d_date_sk + AND store_sales.ss_store_sk = store.s_store_sk + AND store_sales.ss_hdemo_sk = household_demographics.hd_demo_sk + AND store_sales.ss_addr_sk = customer_address.ca_address_sk + AND ( household_demographics.hd_dep_count = 6 + OR household_demographics.hd_vehicle_count = 0 ) + AND date_dim.d_dow IN ( 6, 0 ) + AND date_dim.d_year IN ( 2000, 2000 + 1, 2000 + 2 ) + AND store.s_city IN ( 'Midway', 'Fairview', 'Fairview', + 'Fairview', + 'Fairview' ) + GROUP BY ss_ticket_number, + ss_customer_sk, + ss_addr_sk, + ca_city) dn, + customer, + customer_address current_addr +WHERE ss_customer_sk = c_customer_sk + AND customer.c_current_addr_sk = current_addr.ca_address_sk + AND current_addr.ca_city <> bought_city +ORDER BY c_last_name, + c_first_name, + ca_city, + bought_city, + ss_ticket_number +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query47.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query47.sql new file mode 100644 index 000000000..36ddcbbcb --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query47.sql @@ -0,0 +1,72 @@ +-- start query 47 in stream 0 using template query47.tpl +WITH v1 + AS (SELECT i_category, + i_brand, + s_store_name, + s_company_name, + d_year, + d_moy, + Sum(ss_sales_price) sum_sales, + Avg(Sum(ss_sales_price)) + OVER ( + partition BY i_category, i_brand, s_store_name, + s_company_name, + d_year) + avg_monthly_sales, + Rank() + OVER ( + partition BY i_category, i_brand, s_store_name, + s_company_name + ORDER BY d_year, d_moy) rn + FROM item, + store_sales, + date_dim, + store + WHERE ss_item_sk = i_item_sk + AND ss_sold_date_sk = d_date_sk + AND ss_store_sk = s_store_sk + AND ( d_year = 1999 + OR ( d_year = 1999 - 1 + AND d_moy = 12 ) + OR ( d_year = 1999 + 1 + AND d_moy = 1 ) ) + GROUP BY i_category, + i_brand, + s_store_name, + s_company_name, + d_year, + d_moy), + v2 + AS (SELECT v1.i_category, + v1.d_year, + v1.d_moy, + v1.avg_monthly_sales, + v1.sum_sales, + v1_lag.sum_sales psum, + v1_lead.sum_sales nsum + FROM v1, + v1 v1_lag, + v1 v1_lead + WHERE v1.i_category = v1_lag.i_category + AND v1.i_category = v1_lead.i_category + AND v1.i_brand = v1_lag.i_brand + AND v1.i_brand = v1_lead.i_brand + AND v1.s_store_name = v1_lag.s_store_name + AND v1.s_store_name = v1_lead.s_store_name + AND v1.s_company_name = v1_lag.s_company_name + AND v1.s_company_name = v1_lead.s_company_name + AND v1.rn = v1_lag.rn + 1 + AND v1.rn = v1_lead.rn - 1) +SELECT * +FROM v2 +WHERE d_year = 1999 + AND avg_monthly_sales > 0 + AND CASE + WHEN avg_monthly_sales > 0 THEN Abs(sum_sales - avg_monthly_sales) + / + avg_monthly_sales + ELSE NULL + END > 0.1 +ORDER BY sum_sales - avg_monthly_sales, + 3 +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query48.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query48.sql new file mode 100644 index 000000000..aa8375382 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query48.sql @@ -0,0 +1,34 @@ +-- start query 48 in stream 0 using template query48.tpl +SELECT Sum (ss_quantity) +FROM store_sales, + store, + customer_demographics, + customer_address, + date_dim +WHERE s_store_sk = ss_store_sk + AND ss_sold_date_sk = d_date_sk + AND d_year = 1999 + AND ( ( cd_demo_sk = ss_cdemo_sk + AND cd_marital_status = 'W' + AND cd_education_status = 'Secondary' + AND ss_sales_price BETWEEN 100.00 AND 150.00 ) + OR ( cd_demo_sk = ss_cdemo_sk + AND cd_marital_status = 'M' + AND cd_education_status = 'Advanced Degree' + AND ss_sales_price BETWEEN 50.00 AND 100.00 ) + OR ( cd_demo_sk = ss_cdemo_sk + AND cd_marital_status = 'D' + AND cd_education_status = '2 yr Degree' + AND ss_sales_price BETWEEN 150.00 AND 200.00 ) ) + AND ( ( ss_addr_sk = ca_address_sk + AND ca_country = 'United States' + AND ca_state IN ( 'TX', 'NE', 'MO' ) + AND ss_net_profit BETWEEN 0 AND 2000 ) + OR ( ss_addr_sk = ca_address_sk + AND ca_country = 'United States' + AND ca_state IN ( 'CO', 'TN', 'ND' ) + AND ss_net_profit BETWEEN 150 AND 3000 ) + OR ( ss_addr_sk = ca_address_sk + AND ca_country = 'United States' + AND ca_state IN ( 'OK', 'PA', 'CA' ) + AND ss_net_profit BETWEEN 50 AND 25000 ) ); diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query49.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query49.sql new file mode 100644 index 000000000..9c9eb073c --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query49.sql @@ -0,0 +1,133 @@ +-- start query 49 in stream 0 using template query49.tpl +SELECT 'web' AS channel, + web.item, + web.return_ratio, + web.return_rank, + web.currency_rank +FROM (SELECT item, + return_ratio, + currency_ratio, + Rank() + OVER ( + ORDER BY return_ratio) AS return_rank, + Rank() + OVER ( + ORDER BY currency_ratio) AS currency_rank + FROM (SELECT ws.ws_item_sk AS + item, + ( Cast(Sum(COALESCE(wr.wr_return_quantity, 0)) AS DECIMAL(15, + 4)) / + Cast( + Sum(COALESCE(ws.ws_quantity, 0)) AS DECIMAL(15, 4)) ) AS + return_ratio, + ( Cast(Sum(COALESCE(wr.wr_return_amt, 0)) AS DECIMAL(15, 4)) + / Cast( + Sum( + COALESCE(ws.ws_net_paid, 0)) AS DECIMAL(15, + 4)) ) AS + currency_ratio + FROM web_sales ws + LEFT OUTER JOIN web_returns wr + ON ( ws.ws_order_number = wr.wr_order_number + AND ws.ws_item_sk = wr.wr_item_sk ), + date_dim + WHERE wr.wr_return_amt > 10000 + AND ws.ws_net_profit > 1 + AND ws.ws_net_paid > 0 + AND ws.ws_quantity > 0 + AND ws_sold_date_sk = d_date_sk + AND d_year = 1999 + AND d_moy = 12 + GROUP BY ws.ws_item_sk) in_web) web +WHERE ( web.return_rank <= 10 + OR web.currency_rank <= 10 ) +UNION +SELECT 'catalog' AS channel, + catalog.item, + catalog.return_ratio, + catalog.return_rank, + catalog.currency_rank +FROM (SELECT item, + return_ratio, + currency_ratio, + Rank() + OVER ( + ORDER BY return_ratio) AS return_rank, + Rank() + OVER ( + ORDER BY currency_ratio) AS currency_rank + FROM (SELECT cs.cs_item_sk AS + item, + ( Cast(Sum(COALESCE(cr.cr_return_quantity, 0)) AS DECIMAL(15, + 4)) / + Cast( + Sum(COALESCE(cs.cs_quantity, 0)) AS DECIMAL(15, 4)) ) AS + return_ratio, + ( Cast(Sum(COALESCE(cr.cr_return_amount, 0)) AS DECIMAL(15, 4 + )) / + Cast(Sum( + COALESCE(cs.cs_net_paid, 0)) AS DECIMAL( + 15, 4)) ) AS + currency_ratio + FROM catalog_sales cs + LEFT OUTER JOIN catalog_returns cr + ON ( cs.cs_order_number = cr.cr_order_number + AND cs.cs_item_sk = cr.cr_item_sk ), + date_dim + WHERE cr.cr_return_amount > 10000 + AND cs.cs_net_profit > 1 + AND cs.cs_net_paid > 0 + AND cs.cs_quantity > 0 + AND cs_sold_date_sk = d_date_sk + AND d_year = 1999 + AND d_moy = 12 + GROUP BY cs.cs_item_sk) in_cat) catalog +WHERE ( catalog.return_rank <= 10 + OR catalog.currency_rank <= 10 ) +UNION +SELECT 'store' AS channel, + store.item, + store.return_ratio, + store.return_rank, + store.currency_rank +FROM (SELECT item, + return_ratio, + currency_ratio, + Rank() + OVER ( + ORDER BY return_ratio) AS return_rank, + Rank() + OVER ( + ORDER BY currency_ratio) AS currency_rank + FROM (SELECT sts.ss_item_sk AS + item, + ( Cast(Sum(COALESCE(sr.sr_return_quantity, 0)) AS DECIMAL(15, + 4)) / + Cast( + Sum(COALESCE(sts.ss_quantity, 0)) AS DECIMAL(15, 4)) ) AS + return_ratio, + ( Cast(Sum(COALESCE(sr.sr_return_amt, 0)) AS DECIMAL(15, 4)) + / Cast( + Sum( + COALESCE(sts.ss_net_paid, 0)) AS DECIMAL(15, 4)) ) AS + currency_ratio + FROM store_sales sts + LEFT OUTER JOIN store_returns sr + ON ( sts.ss_ticket_number = + sr.sr_ticket_number + AND sts.ss_item_sk = sr.sr_item_sk ), + date_dim + WHERE sr.sr_return_amt > 10000 + AND sts.ss_net_profit > 1 + AND sts.ss_net_paid > 0 + AND sts.ss_quantity > 0 + AND ss_sold_date_sk = d_date_sk + AND d_year = 1999 + AND d_moy = 12 + GROUP BY sts.ss_item_sk) in_store) store +WHERE ( store.return_rank <= 10 + OR store.currency_rank <= 10 ) +ORDER BY 1, + 4, + 5 +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query5.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query5.sql new file mode 100644 index 000000000..0c03b0ddc --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query5.sql @@ -0,0 +1,127 @@ +-- start query 5 in stream 0 using template query5.tpl +WITH ssr AS +( + SELECT s_store_id, + Sum(sales_price) AS sales, + Sum(profit) AS profit, + Sum(return_amt) AS returns1, + Sum(net_loss) AS profit_loss + FROM ( + SELECT ss_store_sk AS store_sk, + ss_sold_date_sk AS date_sk, + ss_ext_sales_price AS sales_price, + ss_net_profit AS profit, + Cast(0 AS DECIMAL(7,2)) AS return_amt, + Cast(0 AS DECIMAL(7,2)) AS net_loss + FROM store_sales + UNION ALL + SELECT sr_store_sk AS store_sk, + sr_returned_date_sk AS date_sk, + Cast(0 AS DECIMAL(7,2)) AS sales_price, + Cast(0 AS DECIMAL(7,2)) AS profit, + sr_return_amt AS return_amt, + sr_net_loss AS net_loss + FROM store_returns ) salesreturns, + date_dim, + store + WHERE date_sk = d_date_sk + AND d_date BETWEEN Cast('2002-08-22' AS DATE) AND ( + Cast('2002-08-22' AS DATE) + INTERVAL '14' day) + AND store_sk = s_store_sk + GROUP BY s_store_id) , csr AS +( + SELECT cp_catalog_page_id, + sum(sales_price) AS sales, + sum(profit) AS profit, + sum(return_amt) AS returns1, + sum(net_loss) AS profit_loss + FROM ( + SELECT cs_catalog_page_sk AS page_sk, + cs_sold_date_sk AS date_sk, + cs_ext_sales_price AS sales_price, + cs_net_profit AS profit, + cast(0 AS decimal(7,2)) AS return_amt, + cast(0 AS decimal(7,2)) AS net_loss + FROM catalog_sales + UNION ALL + SELECT cr_catalog_page_sk AS page_sk, + cr_returned_date_sk AS date_sk, + cast(0 AS decimal(7,2)) AS sales_price, + cast(0 AS decimal(7,2)) AS profit, + cr_return_amount AS return_amt, + cr_net_loss AS net_loss + FROM catalog_returns ) salesreturns, + date_dim, + catalog_page + WHERE date_sk = d_date_sk + AND d_date BETWEEN cast('2002-08-22' AS date) AND ( + cast('2002-08-22' AS date) + INTERVAL '14' day) + AND page_sk = cp_catalog_page_sk + GROUP BY cp_catalog_page_id) , wsr AS +( + SELECT web_site_id, + sum(sales_price) AS sales, + sum(profit) AS profit, + sum(return_amt) AS returns1, + sum(net_loss) AS profit_loss + FROM ( + SELECT ws_web_site_sk AS wsr_web_site_sk, + ws_sold_date_sk AS date_sk, + ws_ext_sales_price AS sales_price, + ws_net_profit AS profit, + cast(0 AS decimal(7,2)) AS return_amt, + cast(0 AS decimal(7,2)) AS net_loss + FROM web_sales + UNION ALL + SELECT ws_web_site_sk AS wsr_web_site_sk, + wr_returned_date_sk AS date_sk, + cast(0 AS decimal(7,2)) AS sales_price, + cast(0 AS decimal(7,2)) AS profit, + wr_return_amt AS return_amt, + wr_net_loss AS net_loss + FROM web_returns + LEFT OUTER JOIN web_sales + ON ( + wr_item_sk = ws_item_sk + AND wr_order_number = ws_order_number) ) salesreturns, + date_dim, + web_site + WHERE date_sk = d_date_sk + AND d_date BETWEEN cast('2002-08-22' AS date) AND ( + cast('2002-08-22' AS date) + INTERVAL '14' day) + AND wsr_web_site_sk = web_site_sk + GROUP BY web_site_id) +SELECT + channel , + id , + sum(sales) AS sales , + sum(returns1) AS returns1 , + sum(profit) AS profit +FROM ( + SELECT 'store channel' AS channel , + 'store' + || s_store_id AS id , + sales , + returns1 , + (profit - profit_loss) AS profit + FROM ssr + UNION ALL + SELECT 'catalog channel' AS channel , + 'catalog_page' + || cp_catalog_page_id AS id , + sales , + returns1 , + (profit - profit_loss) AS profit + FROM csr + UNION ALL + SELECT 'web channel' AS channel , + 'web_site' + || web_site_id AS id , + sales , + returns1 , + (profit - profit_loss) AS profit + FROM wsr ) x +GROUP BY rollup (channel, id) +ORDER BY channel , + id +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query50.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query50.sql new file mode 100644 index 000000000..e7ddae136 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query50.sql @@ -0,0 +1,71 @@ +-- start query 50 in stream 0 using template query50.tpl +SELECT s_store_name, + s_company_id, + s_street_number, + s_street_name, + s_street_type, + s_suite_number, + s_city, + s_county, + s_state, + s_zip, + Sum(CASE + WHEN ( sr_returned_date_sk - ss_sold_date_sk <= 30 ) THEN 1 + ELSE 0 + END) AS `30 days`, + Sum(CASE + WHEN ( sr_returned_date_sk - ss_sold_date_sk > 30 ) + AND ( sr_returned_date_sk - ss_sold_date_sk <= 60 ) + THEN 1 + ELSE 0 + END) AS `31-60 days`, + Sum(CASE + WHEN ( sr_returned_date_sk - ss_sold_date_sk > 60 ) + AND ( sr_returned_date_sk - ss_sold_date_sk <= 90 ) + THEN 1 + ELSE 0 + END) AS `61-90 days`, + Sum(CASE + WHEN ( sr_returned_date_sk - ss_sold_date_sk > 90 ) + AND ( sr_returned_date_sk - ss_sold_date_sk <= 120 ) + THEN 1 + ELSE 0 + END) AS `91-120 days`, + Sum(CASE + WHEN ( sr_returned_date_sk - ss_sold_date_sk > 120 ) THEN 1 + ELSE 0 + END) AS `>120 days` +FROM store_sales, + store_returns, + store, + date_dim d1, + date_dim d2 +WHERE d2.d_year = 2002 + AND d2.d_moy = 9 + AND ss_ticket_number = sr_ticket_number + AND ss_item_sk = sr_item_sk + AND ss_sold_date_sk = d1.d_date_sk + AND sr_returned_date_sk = d2.d_date_sk + AND ss_customer_sk = sr_customer_sk + AND ss_store_sk = s_store_sk +GROUP BY s_store_name, + s_company_id, + s_street_number, + s_street_name, + s_street_type, + s_suite_number, + s_city, + s_county, + s_state, + s_zip +ORDER BY s_store_name, + s_company_id, + s_street_number, + s_street_name, + s_street_type, + s_suite_number, + s_city, + s_county, + s_state, + s_zip +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query51.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query51.sql new file mode 100644 index 000000000..2c73e7d60 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query51.sql @@ -0,0 +1,54 @@ +-- start query 51 in stream 0 using template query51.tpl +WITH web_v1 AS +( + SELECT ws_item_sk item_sk, + d_date, + sum(Sum(ws_sales_price)) OVER (partition BY ws_item_sk ORDER BY d_date rows BETWEEN UNBOUNDED PRECEDING AND CURRENT row) cume_sales + FROM web_sales , + date_dim + WHERE ws_sold_date_sk=d_date_sk + AND d_month_seq BETWEEN 1192 AND 1192+11 + AND ws_item_sk IS NOT NULL + GROUP BY ws_item_sk, + d_date), store_v1 AS +( + SELECT ss_item_sk item_sk, + d_date, + sum(sum(ss_sales_price)) OVER (partition BY ss_item_sk ORDER BY d_date rows BETWEEN UNBOUNDED PRECEDING AND CURRENT row) cume_sales + FROM store_sales , + date_dim + WHERE ss_sold_date_sk=d_date_sk + AND d_month_seq BETWEEN 1192 AND 1192+11 + AND ss_item_sk IS NOT NULL + GROUP BY ss_item_sk, + d_date) +SELECT + * +FROM ( + SELECT item_sk , + d_date , + web_sales , + store_sales , + max(web_sales) OVER (partition BY item_sk ORDER BY d_date rows BETWEEN UNBOUNDED PRECEDING AND CURRENT row) web_cumulative , + max(store_sales) OVER (partition BY item_sk ORDER BY d_date rows BETWEEN UNBOUNDED PRECEDING AND CURRENT row) store_cumulative + FROM ( + SELECT + CASE + WHEN web.item_sk IS NOT NULL THEN web.item_sk + ELSE store.item_sk + END item_sk , + CASE + WHEN web.d_date IS NOT NULL THEN web.d_date + ELSE store.d_date + END d_date , + web.cume_sales web_sales , + store.cume_sales store_sales + FROM web_v1 web + FULL OUTER JOIN store_v1 store + ON ( + web.item_sk = store.item_sk + AND web.d_date = store.d_date) )x )y +WHERE web_cumulative > store_cumulative +ORDER BY item_sk , + d_date +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query52.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query52.sql new file mode 100644 index 000000000..065f3f169 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query52.sql @@ -0,0 +1,20 @@ +-- start query 52 in stream 0 using template query52.tpl +SELECT dt.d_year, + item.i_brand_id brand_id, + item.i_brand brand, + Sum(ss_ext_sales_price) ext_price +FROM date_dim dt, + store_sales, + item +WHERE dt.d_date_sk = store_sales.ss_sold_date_sk + AND store_sales.ss_item_sk = item.i_item_sk + AND item.i_manager_id = 1 + AND dt.d_moy = 11 + AND dt.d_year = 1999 +GROUP BY dt.d_year, + item.i_brand, + item.i_brand_id +ORDER BY dt.d_year, + ext_price DESC, + brand_id +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query53.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query53.sql new file mode 100644 index 000000000..4c0452a32 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query53.sql @@ -0,0 +1,46 @@ +-- start query 53 in stream 0 using template query53.tpl +SELECT * +FROM (SELECT i_manufact_id, + Sum(ss_sales_price) sum_sales, + Avg(Sum(ss_sales_price)) + OVER ( + partition BY i_manufact_id) avg_quarterly_sales + FROM item, + store_sales, + date_dim, + store + WHERE ss_item_sk = i_item_sk + AND ss_sold_date_sk = d_date_sk + AND ss_store_sk = s_store_sk + AND d_month_seq IN ( 1199, 1199 + 1, 1199 + 2, 1199 + 3, + 1199 + 4, 1199 + 5, 1199 + 6, 1199 + 7, + 1199 + 8, 1199 + 9, 1199 + 10, 1199 + 11 ) + AND ( ( i_category IN ( 'Books', 'Children', 'Electronics' ) + AND i_class IN ( 'personal', 'portable', 'reference', + 'self-help' ) + AND i_brand IN ( 'scholaramalgamalg #14', + 'scholaramalgamalg #7' + , + 'exportiunivamalg #9', + 'scholaramalgamalg #9' ) + ) + OR ( i_category IN ( 'Women', 'Music', 'Men' ) + AND i_class IN ( 'accessories', 'classical', + 'fragrances', + 'pants' ) + AND i_brand IN ( 'amalgimporto #1', + 'edu packscholar #1', + 'exportiimporto #1', + 'importoamalg #1' ) ) ) + GROUP BY i_manufact_id, + d_qoy) tmp1 +WHERE CASE + WHEN avg_quarterly_sales > 0 THEN Abs (sum_sales - avg_quarterly_sales) + / + avg_quarterly_sales + ELSE NULL + END > 0.1 +ORDER BY avg_quarterly_sales, + sum_sales, + i_manufact_id +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query54.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query54.sql new file mode 100644 index 000000000..b776c9590 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query54.sql @@ -0,0 +1,57 @@ +-- start query 54 in stream 0 using template query54.tpl +WITH my_customers + AS (SELECT DISTINCT c_customer_sk, + c_current_addr_sk + FROM (SELECT cs_sold_date_sk sold_date_sk, + cs_bill_customer_sk customer_sk, + cs_item_sk item_sk + FROM catalog_sales + UNION ALL + SELECT ws_sold_date_sk sold_date_sk, + ws_bill_customer_sk customer_sk, + ws_item_sk item_sk + FROM web_sales) cs_or_ws_sales, + item, + date_dim, + customer + WHERE sold_date_sk = d_date_sk + AND item_sk = i_item_sk + AND i_category = 'Sports' + AND i_class = 'fitness' + AND c_customer_sk = cs_or_ws_sales.customer_sk + AND d_moy = 5 + AND d_year = 2000), + my_revenue + AS (SELECT c_customer_sk, + Sum(ss_ext_sales_price) AS revenue + FROM my_customers, + store_sales, + customer_address, + store, + date_dim + WHERE c_current_addr_sk = ca_address_sk + AND ca_county = s_county + AND ca_state = s_state + AND ss_sold_date_sk = d_date_sk + AND c_customer_sk = ss_customer_sk + AND d_month_seq BETWEEN (SELECT DISTINCT d_month_seq + 1 + FROM date_dim + WHERE d_year = 2000 + AND d_moy = 5) AND + (SELECT DISTINCT + d_month_seq + 3 + FROM date_dim + WHERE d_year = 2000 + AND d_moy = 5) + GROUP BY c_customer_sk), + segments + AS (SELECT Cast(( revenue / 50 ) AS INT) AS segment + FROM my_revenue) +SELECT segment, + Count(*) AS num_customers, + segment * 50 AS segment_base +FROM segments +GROUP BY segment +ORDER BY segment, + num_customers +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query55.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query55.sql new file mode 100644 index 000000000..37ed2563f --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query55.sql @@ -0,0 +1,17 @@ +-- start query 55 in stream 0 using template query55.tpl +SELECT i_brand_id brand_id, + i_brand brand, + Sum(ss_ext_sales_price) ext_price +FROM date_dim, + store_sales, + item +WHERE d_date_sk = ss_sold_date_sk + AND ss_item_sk = i_item_sk + AND i_manager_id = 33 + AND d_moy = 12 + AND d_year = 1998 +GROUP BY i_brand, + i_brand_id +ORDER BY ext_price DESC, + i_brand_id +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query56.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query56.sql new file mode 100644 index 000000000..7d2ad0629 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query56.sql @@ -0,0 +1,68 @@ +-- start query 56 in stream 0 using template query56.tpl +WITH ss + AS (SELECT i_item_id, + Sum(ss_ext_sales_price) total_sales + FROM store_sales, + date_dim, + customer_address, + item + WHERE i_item_id IN (SELECT i_item_id + FROM item + WHERE i_color IN ( 'firebrick', 'rosy', 'white' ) + ) + AND ss_item_sk = i_item_sk + AND ss_sold_date_sk = d_date_sk + AND d_year = 1998 + AND d_moy = 3 + AND ss_addr_sk = ca_address_sk + AND ca_gmt_offset = -6 + GROUP BY i_item_id), + cs + AS (SELECT i_item_id, + Sum(cs_ext_sales_price) total_sales + FROM catalog_sales, + date_dim, + customer_address, + item + WHERE i_item_id IN (SELECT i_item_id + FROM item + WHERE i_color IN ( 'firebrick', 'rosy', 'white' ) + ) + AND cs_item_sk = i_item_sk + AND cs_sold_date_sk = d_date_sk + AND d_year = 1998 + AND d_moy = 3 + AND cs_bill_addr_sk = ca_address_sk + AND ca_gmt_offset = -6 + GROUP BY i_item_id), + ws + AS (SELECT i_item_id, + Sum(ws_ext_sales_price) total_sales + FROM web_sales, + date_dim, + customer_address, + item + WHERE i_item_id IN (SELECT i_item_id + FROM item + WHERE i_color IN ( 'firebrick', 'rosy', 'white' ) + ) + AND ws_item_sk = i_item_sk + AND ws_sold_date_sk = d_date_sk + AND d_year = 1998 + AND d_moy = 3 + AND ws_bill_addr_sk = ca_address_sk + AND ca_gmt_offset = -6 + GROUP BY i_item_id) +SELECT i_item_id, + Sum(total_sales) total_sales +FROM (SELECT * + FROM ss + UNION ALL + SELECT * + FROM cs + UNION ALL + SELECT * + FROM ws) tmp1 +GROUP BY i_item_id +ORDER BY total_sales +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query57.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query57.sql new file mode 100644 index 000000000..5cb73bce4 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query57.sql @@ -0,0 +1,66 @@ +-- start query 57 in stream 0 using template query57.tpl +WITH v1 + AS (SELECT i_category, + i_brand, + cc_name, + d_year, + d_moy, + Sum(cs_sales_price) sum_sales + , + Avg(Sum(cs_sales_price)) + OVER ( + partition BY i_category, i_brand, cc_name, d_year) + avg_monthly_sales + , + Rank() + OVER ( + partition BY i_category, i_brand, cc_name + ORDER BY d_year, d_moy) rn + FROM item, + catalog_sales, + date_dim, + call_center + WHERE cs_item_sk = i_item_sk + AND cs_sold_date_sk = d_date_sk + AND cc_call_center_sk = cs_call_center_sk + AND ( d_year = 2000 + OR ( d_year = 2000 - 1 + AND d_moy = 12 ) + OR ( d_year = 2000 + 1 + AND d_moy = 1 ) ) + GROUP BY i_category, + i_brand, + cc_name, + d_year, + d_moy), + v2 + AS (SELECT v1.i_brand, + v1.d_year, + v1.avg_monthly_sales, + v1.sum_sales, + v1_lag.sum_sales psum, + v1_lead.sum_sales nsum + FROM v1, + v1 v1_lag, + v1 v1_lead + WHERE v1.i_category = v1_lag.i_category + AND v1.i_category = v1_lead.i_category + AND v1.i_brand = v1_lag.i_brand + AND v1.i_brand = v1_lead.i_brand + AND v1. cc_name = v1_lag. cc_name + AND v1. cc_name = v1_lead. cc_name + AND v1.rn = v1_lag.rn + 1 + AND v1.rn = v1_lead.rn - 1) +SELECT * +FROM v2 +WHERE d_year = 2000 + AND avg_monthly_sales > 0 + AND CASE + WHEN avg_monthly_sales > 0 THEN Abs(sum_sales - avg_monthly_sales) + / + avg_monthly_sales + ELSE NULL + END > 0.1 +ORDER BY sum_sales - avg_monthly_sales, + 3 +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query58.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query58.sql new file mode 100644 index 000000000..8611390de --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query58.sql @@ -0,0 +1,72 @@ +-- start query 58 in stream 0 using template query58.tpl +WITH ss_items + AS (SELECT i_item_id item_id, + Sum(ss_ext_sales_price) ss_item_rev + FROM store_sales, + item, + date_dim + WHERE ss_item_sk = i_item_sk + AND d_date IN (SELECT d_date + FROM date_dim + WHERE d_week_seq = (SELECT d_week_seq + FROM date_dim + WHERE d_date = '2002-02-25' + )) + AND ss_sold_date_sk = d_date_sk + GROUP BY i_item_id), + cs_items + AS (SELECT i_item_id item_id, + Sum(cs_ext_sales_price) cs_item_rev + FROM catalog_sales, + item, + date_dim + WHERE cs_item_sk = i_item_sk + AND d_date IN (SELECT d_date + FROM date_dim + WHERE d_week_seq = (SELECT d_week_seq + FROM date_dim + WHERE d_date = '2002-02-25' + )) + AND cs_sold_date_sk = d_date_sk + GROUP BY i_item_id), + ws_items + AS (SELECT i_item_id item_id, + Sum(ws_ext_sales_price) ws_item_rev + FROM web_sales, + item, + date_dim + WHERE ws_item_sk = i_item_sk + AND d_date IN (SELECT d_date + FROM date_dim + WHERE d_week_seq = (SELECT d_week_seq + FROM date_dim + WHERE d_date = '2002-02-25' + )) + AND ws_sold_date_sk = d_date_sk + GROUP BY i_item_id) +SELECT ss_items.item_id, + ss_item_rev, + ss_item_rev / ( ss_item_rev + cs_item_rev + ws_item_rev ) / 3 * + 100 ss_dev, + cs_item_rev, + cs_item_rev / ( ss_item_rev + cs_item_rev + ws_item_rev ) / 3 * + 100 cs_dev, + ws_item_rev, + ws_item_rev / ( ss_item_rev + cs_item_rev + ws_item_rev ) / 3 * + 100 ws_dev, + ( ss_item_rev + cs_item_rev + ws_item_rev ) / 3 + average +FROM ss_items, + cs_items, + ws_items +WHERE ss_items.item_id = cs_items.item_id + AND ss_items.item_id = ws_items.item_id + AND ss_item_rev BETWEEN 0.9 * cs_item_rev AND 1.1 * cs_item_rev + AND ss_item_rev BETWEEN 0.9 * ws_item_rev AND 1.1 * ws_item_rev + AND cs_item_rev BETWEEN 0.9 * ss_item_rev AND 1.1 * ss_item_rev + AND cs_item_rev BETWEEN 0.9 * ws_item_rev AND 1.1 * ws_item_rev + AND ws_item_rev BETWEEN 0.9 * ss_item_rev AND 1.1 * ss_item_rev + AND ws_item_rev BETWEEN 0.9 * cs_item_rev AND 1.1 * cs_item_rev +ORDER BY item_id, + ss_item_rev +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query59.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query59.sql new file mode 100644 index 000000000..3caa54048 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query59.sql @@ -0,0 +1,85 @@ +-- start query 59 in stream 0 using template query59.tpl +WITH wss + AS (SELECT d_week_seq, + ss_store_sk, + Sum(CASE + WHEN ( d_day_name = 'Sunday' ) THEN ss_sales_price + ELSE NULL + END) sun_sales, + Sum(CASE + WHEN ( d_day_name = 'Monday' ) THEN ss_sales_price + ELSE NULL + END) mon_sales, + Sum(CASE + WHEN ( d_day_name = 'Tuesday' ) THEN ss_sales_price + ELSE NULL + END) tue_sales, + Sum(CASE + WHEN ( d_day_name = 'Wednesday' ) THEN ss_sales_price + ELSE NULL + END) wed_sales, + Sum(CASE + WHEN ( d_day_name = 'Thursday' ) THEN ss_sales_price + ELSE NULL + END) thu_sales, + Sum(CASE + WHEN ( d_day_name = 'Friday' ) THEN ss_sales_price + ELSE NULL + END) fri_sales, + Sum(CASE + WHEN ( d_day_name = 'Saturday' ) THEN ss_sales_price + ELSE NULL + END) sat_sales + FROM store_sales, + date_dim + WHERE d_date_sk = ss_sold_date_sk + GROUP BY d_week_seq, + ss_store_sk) +SELECT s_store_name1, + s_store_id1, + d_week_seq1, + sun_sales1 / sun_sales2, + mon_sales1 / mon_sales2, + tue_sales1 / tue_sales2, + wed_sales1 / wed_sales2, + thu_sales1 / thu_sales2, + fri_sales1 / fri_sales2, + sat_sales1 / sat_sales2 +FROM (SELECT s_store_name s_store_name1, + wss.d_week_seq d_week_seq1, + s_store_id s_store_id1, + sun_sales sun_sales1, + mon_sales mon_sales1, + tue_sales tue_sales1, + wed_sales wed_sales1, + thu_sales thu_sales1, + fri_sales fri_sales1, + sat_sales sat_sales1 + FROM wss, + store, + date_dim d + WHERE d.d_week_seq = wss.d_week_seq + AND ss_store_sk = s_store_sk + AND d_month_seq BETWEEN 1196 AND 1196 + 11) y, + (SELECT s_store_name s_store_name2, + wss.d_week_seq d_week_seq2, + s_store_id s_store_id2, + sun_sales sun_sales2, + mon_sales mon_sales2, + tue_sales tue_sales2, + wed_sales wed_sales2, + thu_sales thu_sales2, + fri_sales fri_sales2, + sat_sales sat_sales2 + FROM wss, + store, + date_dim d + WHERE d.d_week_seq = wss.d_week_seq + AND ss_store_sk = s_store_sk + AND d_month_seq BETWEEN 1196 + 12 AND 1196 + 23) x +WHERE s_store_id1 = s_store_id2 + AND d_week_seq1 = d_week_seq2 - 52 +ORDER BY s_store_name1, + s_store_id1, + d_week_seq1 +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query6.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query6.sql new file mode 100644 index 000000000..1739ea575 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query6.sql @@ -0,0 +1,23 @@ +-- start query 6 in stream 0 using template query6.tpl +SELECT a.ca_state state, + Count(*) cnt +FROM customer_address a, + customer c, + store_sales s, + date_dim d, + item i +WHERE a.ca_address_sk = c.c_current_addr_sk + AND c.c_customer_sk = s.ss_customer_sk + AND s.ss_sold_date_sk = d.d_date_sk + AND s.ss_item_sk = i.i_item_sk + AND d.d_month_seq = (SELECT DISTINCT ( d_month_seq ) + FROM date_dim + WHERE d_year = 1998 + AND d_moy = 7) + AND i.i_current_price > 1.2 * (SELECT Avg(j.i_current_price) + FROM item j + WHERE j.i_category = i.i_category) +GROUP BY a.ca_state +HAVING Count(*) >= 10 +ORDER BY cnt +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query60.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query60.sql new file mode 100644 index 000000000..541108dea --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query60.sql @@ -0,0 +1,66 @@ +-- start query 60 in stream 0 using template query60.tpl +WITH ss + AS (SELECT i_item_id, + Sum(ss_ext_sales_price) total_sales + FROM store_sales, + date_dim, + customer_address, + item + WHERE i_item_id IN (SELECT i_item_id + FROM item + WHERE i_category IN ( 'Jewelry' )) + AND ss_item_sk = i_item_sk + AND ss_sold_date_sk = d_date_sk + AND d_year = 1999 + AND d_moy = 8 + AND ss_addr_sk = ca_address_sk + AND ca_gmt_offset = -6 + GROUP BY i_item_id), + cs + AS (SELECT i_item_id, + Sum(cs_ext_sales_price) total_sales + FROM catalog_sales, + date_dim, + customer_address, + item + WHERE i_item_id IN (SELECT i_item_id + FROM item + WHERE i_category IN ( 'Jewelry' )) + AND cs_item_sk = i_item_sk + AND cs_sold_date_sk = d_date_sk + AND d_year = 1999 + AND d_moy = 8 + AND cs_bill_addr_sk = ca_address_sk + AND ca_gmt_offset = -6 + GROUP BY i_item_id), + ws + AS (SELECT i_item_id, + Sum(ws_ext_sales_price) total_sales + FROM web_sales, + date_dim, + customer_address, + item + WHERE i_item_id IN (SELECT i_item_id + FROM item + WHERE i_category IN ( 'Jewelry' )) + AND ws_item_sk = i_item_sk + AND ws_sold_date_sk = d_date_sk + AND d_year = 1999 + AND d_moy = 8 + AND ws_bill_addr_sk = ca_address_sk + AND ca_gmt_offset = -6 + GROUP BY i_item_id) +SELECT i_item_id, + Sum(total_sales) total_sales +FROM (SELECT * + FROM ss + UNION ALL + SELECT * + FROM cs + UNION ALL + SELECT * + FROM ws) tmp1 +GROUP BY i_item_id +ORDER BY i_item_id, + total_sales +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query61.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query61.sql new file mode 100644 index 000000000..d6d50753c --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query61.sql @@ -0,0 +1,47 @@ +-- start query 61 in stream 0 using template query61.tpl +SELECT promotions, + total, + Cast(promotions AS DECIMAL(15, 4)) / + Cast(total AS DECIMAL(15, 4)) * 100 +FROM (SELECT Sum(ss_ext_sales_price) promotions + FROM store_sales, + store, + promotion, + date_dim, + customer, + customer_address, + item + WHERE ss_sold_date_sk = d_date_sk + AND ss_store_sk = s_store_sk + AND ss_promo_sk = p_promo_sk + AND ss_customer_sk = c_customer_sk + AND ca_address_sk = c_current_addr_sk + AND ss_item_sk = i_item_sk + AND ca_gmt_offset = -7 + AND i_category = 'Books' + AND ( p_channel_dmail = 'Y' + OR p_channel_email = 'Y' + OR p_channel_tv = 'Y' ) + AND s_gmt_offset = -7 + AND d_year = 2001 + AND d_moy = 12) promotional_sales, + (SELECT Sum(ss_ext_sales_price) total + FROM store_sales, + store, + date_dim, + customer, + customer_address, + item + WHERE ss_sold_date_sk = d_date_sk + AND ss_store_sk = s_store_sk + AND ss_customer_sk = c_customer_sk + AND ca_address_sk = c_current_addr_sk + AND ss_item_sk = i_item_sk + AND ca_gmt_offset = -7 + AND i_category = 'Books' + AND s_gmt_offset = -7 + AND d_year = 2001 + AND d_moy = 12) all_sales +ORDER BY promotions, + total +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query62.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query62.sql new file mode 100644 index 000000000..f83715e73 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query62.sql @@ -0,0 +1,45 @@ +-- start query 62 in stream 0 using template query62.tpl +SELECT Substr(w_warehouse_name, 1, 20), + sm_type, + web_name, + Sum(CASE + WHEN ( ws_ship_date_sk - ws_sold_date_sk <= 30 ) THEN 1 + ELSE 0 + END) AS `30 days`, + Sum(CASE + WHEN ( ws_ship_date_sk - ws_sold_date_sk > 30 ) + AND ( ws_ship_date_sk - ws_sold_date_sk <= 60 ) THEN 1 + ELSE 0 + END) AS `31-60 days`, + Sum(CASE + WHEN ( ws_ship_date_sk - ws_sold_date_sk > 60 ) + AND ( ws_ship_date_sk - ws_sold_date_sk <= 90 ) THEN 1 + ELSE 0 + END) AS `61-90 days`, + Sum(CASE + WHEN ( ws_ship_date_sk - ws_sold_date_sk > 90 ) + AND ( ws_ship_date_sk - ws_sold_date_sk <= 120 ) THEN + 1 + ELSE 0 + END) AS `91-120 days`, + Sum(CASE + WHEN ( ws_ship_date_sk - ws_sold_date_sk > 120 ) THEN 1 + ELSE 0 + END) AS `>120 days` +FROM web_sales, + warehouse, + ship_mode, + web_site, + date_dim +WHERE d_month_seq BETWEEN 1222 AND 1222 + 11 + AND ws_ship_date_sk = d_date_sk + AND ws_warehouse_sk = w_warehouse_sk + AND ws_ship_mode_sk = sm_ship_mode_sk + AND ws_web_site_sk = web_site_sk +GROUP BY Substr(w_warehouse_name, 1, 20), + sm_type, + web_name +ORDER BY Substr(w_warehouse_name, 1, 20), + sm_type, + web_name +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query63.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query63.sql new file mode 100644 index 000000000..8706bb492 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query63.sql @@ -0,0 +1,45 @@ +-- start query 63 in stream 0 using template query63.tpl +SELECT * +FROM (SELECT i_manager_id, + Sum(ss_sales_price) sum_sales, + Avg(Sum(ss_sales_price)) + OVER ( + partition BY i_manager_id) avg_monthly_sales + FROM item, + store_sales, + date_dim, + store + WHERE ss_item_sk = i_item_sk + AND ss_sold_date_sk = d_date_sk + AND ss_store_sk = s_store_sk + AND d_month_seq IN ( 1200, 1200 + 1, 1200 + 2, 1200 + 3, + 1200 + 4, 1200 + 5, 1200 + 6, 1200 + 7, + 1200 + 8, 1200 + 9, 1200 + 10, 1200 + 11 ) + AND ( ( i_category IN ( 'Books', 'Children', 'Electronics' ) + AND i_class IN ( 'personal', 'portable', 'reference', + 'self-help' ) + AND i_brand IN ( 'scholaramalgamalg #14', + 'scholaramalgamalg #7' + , + 'exportiunivamalg #9', + 'scholaramalgamalg #9' ) + ) + OR ( i_category IN ( 'Women', 'Music', 'Men' ) + AND i_class IN ( 'accessories', 'classical', + 'fragrances', + 'pants' ) + AND i_brand IN ( 'amalgimporto #1', + 'edu packscholar #1', + 'exportiimporto #1', + 'importoamalg #1' ) ) ) + GROUP BY i_manager_id, + d_moy) tmp1 +WHERE CASE + WHEN avg_monthly_sales > 0 THEN Abs (sum_sales - avg_monthly_sales) / + avg_monthly_sales + ELSE NULL + END > 0.1 +ORDER BY i_manager_id, + avg_monthly_sales, + sum_sales +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query64.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query64.sql new file mode 100644 index 000000000..0c5d1d3d8 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query64.sql @@ -0,0 +1,122 @@ +-- start query 64 in stream 0 using template query64.tpl +WITH cs_ui + AS (SELECT cs_item_sk, + Sum(cs_ext_list_price) AS sale, + Sum(cr_refunded_cash + cr_reversed_charge + + cr_store_credit) AS refund + FROM catalog_sales, + catalog_returns + WHERE cs_item_sk = cr_item_sk + AND cs_order_number = cr_order_number + GROUP BY cs_item_sk + HAVING Sum(cs_ext_list_price) > 2 * Sum( + cr_refunded_cash + cr_reversed_charge + + cr_store_credit)), + cross_sales + AS (SELECT i_product_name product_name, + i_item_sk item_sk, + s_store_name store_name, + s_zip store_zip, + ad1.ca_street_number b_street_number, + ad1.ca_street_name b_streen_name, + ad1.ca_city b_city, + ad1.ca_zip b_zip, + ad2.ca_street_number c_street_number, + ad2.ca_street_name c_street_name, + ad2.ca_city c_city, + ad2.ca_zip c_zip, + d1.d_year AS syear, + d2.d_year AS fsyear, + d3.d_year s2year, + Count(*) cnt, + Sum(ss_wholesale_cost) s1, + Sum(ss_list_price) s2, + Sum(ss_coupon_amt) s3 + FROM store_sales, + store_returns, + cs_ui, + date_dim d1, + date_dim d2, + date_dim d3, + store, + customer, + customer_demographics cd1, + customer_demographics cd2, + promotion, + household_demographics hd1, + household_demographics hd2, + customer_address ad1, + customer_address ad2, + income_band ib1, + income_band ib2, + item + WHERE ss_store_sk = s_store_sk + AND ss_sold_date_sk = d1.d_date_sk + AND ss_customer_sk = c_customer_sk + AND ss_cdemo_sk = cd1.cd_demo_sk + AND ss_hdemo_sk = hd1.hd_demo_sk + AND ss_addr_sk = ad1.ca_address_sk + AND ss_item_sk = i_item_sk + AND ss_item_sk = sr_item_sk + AND ss_ticket_number = sr_ticket_number + AND ss_item_sk = cs_ui.cs_item_sk + AND c_current_cdemo_sk = cd2.cd_demo_sk + AND c_current_hdemo_sk = hd2.hd_demo_sk + AND c_current_addr_sk = ad2.ca_address_sk + AND c_first_sales_date_sk = d2.d_date_sk + AND c_first_shipto_date_sk = d3.d_date_sk + AND ss_promo_sk = p_promo_sk + AND hd1.hd_income_band_sk = ib1.ib_income_band_sk + AND hd2.hd_income_band_sk = ib2.ib_income_band_sk + AND cd1.cd_marital_status <> cd2.cd_marital_status + AND i_color IN ( 'cyan', 'peach', 'blush', 'frosted', + 'powder', 'orange' ) + AND i_current_price BETWEEN 58 AND 58 + 10 + AND i_current_price BETWEEN 58 + 1 AND 58 + 15 + GROUP BY i_product_name, + i_item_sk, + s_store_name, + s_zip, + ad1.ca_street_number, + ad1.ca_street_name, + ad1.ca_city, + ad1.ca_zip, + ad2.ca_street_number, + ad2.ca_street_name, + ad2.ca_city, + ad2.ca_zip, + d1.d_year, + d2.d_year, + d3.d_year) +SELECT cs1.product_name, + cs1.store_name, + cs1.store_zip, + cs1.b_street_number, + cs1.b_streen_name, + cs1.b_city, + cs1.b_zip, + cs1.c_street_number, + cs1.c_street_name, + cs1.c_city, + cs1.c_zip, + cs1.syear, + cs1.cnt, + cs1.s1, + cs1.s2, + cs1.s3, + cs2.s1, + cs2.s2, + cs2.s3, + cs2.syear, + cs2.cnt +FROM cross_sales cs1, + cross_sales cs2 +WHERE cs1.item_sk = cs2.item_sk + AND cs1.syear = 2001 + AND cs2.syear = 2001 + 1 + AND cs2.cnt <= cs1.cnt + AND cs1.store_name = cs2.store_name + AND cs1.store_zip = cs2.store_zip +ORDER BY cs1.product_name, + cs1.store_name, + cs2.cnt; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query65.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query65.sql new file mode 100644 index 000000000..399f128c3 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query65.sql @@ -0,0 +1,37 @@ +-- start query 65 in stream 0 using template query65.tpl +SELECT s_store_name, + i_item_desc, + sc.revenue, + i_current_price, + i_wholesale_cost, + i_brand +FROM store, + item, + (SELECT ss_store_sk, + Avg(revenue) AS ave + FROM (SELECT ss_store_sk, + ss_item_sk, + Sum(ss_sales_price) AS revenue + FROM store_sales, + date_dim + WHERE ss_sold_date_sk = d_date_sk + AND d_month_seq BETWEEN 1199 AND 1199 + 11 + GROUP BY ss_store_sk, + ss_item_sk) sa + GROUP BY ss_store_sk) sb, + (SELECT ss_store_sk, + ss_item_sk, + Sum(ss_sales_price) AS revenue + FROM store_sales, + date_dim + WHERE ss_sold_date_sk = d_date_sk + AND d_month_seq BETWEEN 1199 AND 1199 + 11 + GROUP BY ss_store_sk, + ss_item_sk) sc +WHERE sb.ss_store_sk = sc.ss_store_sk + AND sc.revenue <= 0.1 * sb.ave + AND s_store_sk = sc.ss_store_sk + AND i_item_sk = sc.ss_item_sk +ORDER BY s_store_name, + i_item_desc +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query66.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query66.sql new file mode 100644 index 000000000..79446555f --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query66.sql @@ -0,0 +1,306 @@ +-- start query 66 in stream 0 using template query66.tpl +SELECT w_warehouse_name, + w_warehouse_sq_ft, + w_city, + w_county, + w_state, + w_country, + ship_carriers, + year1, + Sum(jan_sales) AS jan_sales, + Sum(feb_sales) AS feb_sales, + Sum(mar_sales) AS mar_sales, + Sum(apr_sales) AS apr_sales, + Sum(may_sales) AS may_sales, + Sum(jun_sales) AS jun_sales, + Sum(jul_sales) AS jul_sales, + Sum(aug_sales) AS aug_sales, + Sum(sep_sales) AS sep_sales, + Sum(oct_sales) AS oct_sales, + Sum(nov_sales) AS nov_sales, + Sum(dec_sales) AS dec_sales, + Sum(jan_sales / w_warehouse_sq_ft) AS jan_sales_per_sq_foot, + Sum(feb_sales / w_warehouse_sq_ft) AS feb_sales_per_sq_foot, + Sum(mar_sales / w_warehouse_sq_ft) AS mar_sales_per_sq_foot, + Sum(apr_sales / w_warehouse_sq_ft) AS apr_sales_per_sq_foot, + Sum(may_sales / w_warehouse_sq_ft) AS may_sales_per_sq_foot, + Sum(jun_sales / w_warehouse_sq_ft) AS jun_sales_per_sq_foot, + Sum(jul_sales / w_warehouse_sq_ft) AS jul_sales_per_sq_foot, + Sum(aug_sales / w_warehouse_sq_ft) AS aug_sales_per_sq_foot, + Sum(sep_sales / w_warehouse_sq_ft) AS sep_sales_per_sq_foot, + Sum(oct_sales / w_warehouse_sq_ft) AS oct_sales_per_sq_foot, + Sum(nov_sales / w_warehouse_sq_ft) AS nov_sales_per_sq_foot, + Sum(dec_sales / w_warehouse_sq_ft) AS dec_sales_per_sq_foot, + Sum(jan_net) AS jan_net, + Sum(feb_net) AS feb_net, + Sum(mar_net) AS mar_net, + Sum(apr_net) AS apr_net, + Sum(may_net) AS may_net, + Sum(jun_net) AS jun_net, + Sum(jul_net) AS jul_net, + Sum(aug_net) AS aug_net, + Sum(sep_net) AS sep_net, + Sum(oct_net) AS oct_net, + Sum(nov_net) AS nov_net, + Sum(dec_net) AS dec_net +FROM (SELECT w_warehouse_name, + w_warehouse_sq_ft, + w_city, + w_county, + w_state, + w_country, + 'ZOUROS' + || ',' + || 'ZHOU' AS ship_carriers, + d_year AS year1, + Sum(CASE + WHEN d_moy = 1 THEN ws_ext_sales_price * ws_quantity + ELSE 0 + END) AS jan_sales, + Sum(CASE + WHEN d_moy = 2 THEN ws_ext_sales_price * ws_quantity + ELSE 0 + END) AS feb_sales, + Sum(CASE + WHEN d_moy = 3 THEN ws_ext_sales_price * ws_quantity + ELSE 0 + END) AS mar_sales, + Sum(CASE + WHEN d_moy = 4 THEN ws_ext_sales_price * ws_quantity + ELSE 0 + END) AS apr_sales, + Sum(CASE + WHEN d_moy = 5 THEN ws_ext_sales_price * ws_quantity + ELSE 0 + END) AS may_sales, + Sum(CASE + WHEN d_moy = 6 THEN ws_ext_sales_price * ws_quantity + ELSE 0 + END) AS jun_sales, + Sum(CASE + WHEN d_moy = 7 THEN ws_ext_sales_price * ws_quantity + ELSE 0 + END) AS jul_sales, + Sum(CASE + WHEN d_moy = 8 THEN ws_ext_sales_price * ws_quantity + ELSE 0 + END) AS aug_sales, + Sum(CASE + WHEN d_moy = 9 THEN ws_ext_sales_price * ws_quantity + ELSE 0 + END) AS sep_sales, + Sum(CASE + WHEN d_moy = 10 THEN ws_ext_sales_price * ws_quantity + ELSE 0 + END) AS oct_sales, + Sum(CASE + WHEN d_moy = 11 THEN ws_ext_sales_price * ws_quantity + ELSE 0 + END) AS nov_sales, + Sum(CASE + WHEN d_moy = 12 THEN ws_ext_sales_price * ws_quantity + ELSE 0 + END) AS dec_sales, + Sum(CASE + WHEN d_moy = 1 THEN ws_net_paid_inc_ship * ws_quantity + ELSE 0 + END) AS jan_net, + Sum(CASE + WHEN d_moy = 2 THEN ws_net_paid_inc_ship * ws_quantity + ELSE 0 + END) AS feb_net, + Sum(CASE + WHEN d_moy = 3 THEN ws_net_paid_inc_ship * ws_quantity + ELSE 0 + END) AS mar_net, + Sum(CASE + WHEN d_moy = 4 THEN ws_net_paid_inc_ship * ws_quantity + ELSE 0 + END) AS apr_net, + Sum(CASE + WHEN d_moy = 5 THEN ws_net_paid_inc_ship * ws_quantity + ELSE 0 + END) AS may_net, + Sum(CASE + WHEN d_moy = 6 THEN ws_net_paid_inc_ship * ws_quantity + ELSE 0 + END) AS jun_net, + Sum(CASE + WHEN d_moy = 7 THEN ws_net_paid_inc_ship * ws_quantity + ELSE 0 + END) AS jul_net, + Sum(CASE + WHEN d_moy = 8 THEN ws_net_paid_inc_ship * ws_quantity + ELSE 0 + END) AS aug_net, + Sum(CASE + WHEN d_moy = 9 THEN ws_net_paid_inc_ship * ws_quantity + ELSE 0 + END) AS sep_net, + Sum(CASE + WHEN d_moy = 10 THEN ws_net_paid_inc_ship * ws_quantity + ELSE 0 + END) AS oct_net, + Sum(CASE + WHEN d_moy = 11 THEN ws_net_paid_inc_ship * ws_quantity + ELSE 0 + END) AS nov_net, + Sum(CASE + WHEN d_moy = 12 THEN ws_net_paid_inc_ship * ws_quantity + ELSE 0 + END) AS dec_net + FROM web_sales, + warehouse, + date_dim, + time_dim, + ship_mode + WHERE ws_warehouse_sk = w_warehouse_sk + AND ws_sold_date_sk = d_date_sk + AND ws_sold_time_sk = t_time_sk + AND ws_ship_mode_sk = sm_ship_mode_sk + AND d_year = 1998 + AND t_time BETWEEN 7249 AND 7249 + 28800 + AND sm_carrier IN ( 'ZOUROS', 'ZHOU' ) + GROUP BY w_warehouse_name, + w_warehouse_sq_ft, + w_city, + w_county, + w_state, + w_country, + d_year + UNION ALL + SELECT w_warehouse_name, + w_warehouse_sq_ft, + w_city, + w_county, + w_state, + w_country, + 'ZOUROS' + || ',' + || 'ZHOU' AS ship_carriers, + d_year AS year1, + Sum(CASE + WHEN d_moy = 1 THEN cs_ext_sales_price * cs_quantity + ELSE 0 + END) AS jan_sales, + Sum(CASE + WHEN d_moy = 2 THEN cs_ext_sales_price * cs_quantity + ELSE 0 + END) AS feb_sales, + Sum(CASE + WHEN d_moy = 3 THEN cs_ext_sales_price * cs_quantity + ELSE 0 + END) AS mar_sales, + Sum(CASE + WHEN d_moy = 4 THEN cs_ext_sales_price * cs_quantity + ELSE 0 + END) AS apr_sales, + Sum(CASE + WHEN d_moy = 5 THEN cs_ext_sales_price * cs_quantity + ELSE 0 + END) AS may_sales, + Sum(CASE + WHEN d_moy = 6 THEN cs_ext_sales_price * cs_quantity + ELSE 0 + END) AS jun_sales, + Sum(CASE + WHEN d_moy = 7 THEN cs_ext_sales_price * cs_quantity + ELSE 0 + END) AS jul_sales, + Sum(CASE + WHEN d_moy = 8 THEN cs_ext_sales_price * cs_quantity + ELSE 0 + END) AS aug_sales, + Sum(CASE + WHEN d_moy = 9 THEN cs_ext_sales_price * cs_quantity + ELSE 0 + END) AS sep_sales, + Sum(CASE + WHEN d_moy = 10 THEN cs_ext_sales_price * cs_quantity + ELSE 0 + END) AS oct_sales, + Sum(CASE + WHEN d_moy = 11 THEN cs_ext_sales_price * cs_quantity + ELSE 0 + END) AS nov_sales, + Sum(CASE + WHEN d_moy = 12 THEN cs_ext_sales_price * cs_quantity + ELSE 0 + END) AS dec_sales, + Sum(CASE + WHEN d_moy = 1 THEN cs_net_paid * cs_quantity + ELSE 0 + END) AS jan_net, + Sum(CASE + WHEN d_moy = 2 THEN cs_net_paid * cs_quantity + ELSE 0 + END) AS feb_net, + Sum(CASE + WHEN d_moy = 3 THEN cs_net_paid * cs_quantity + ELSE 0 + END) AS mar_net, + Sum(CASE + WHEN d_moy = 4 THEN cs_net_paid * cs_quantity + ELSE 0 + END) AS apr_net, + Sum(CASE + WHEN d_moy = 5 THEN cs_net_paid * cs_quantity + ELSE 0 + END) AS may_net, + Sum(CASE + WHEN d_moy = 6 THEN cs_net_paid * cs_quantity + ELSE 0 + END) AS jun_net, + Sum(CASE + WHEN d_moy = 7 THEN cs_net_paid * cs_quantity + ELSE 0 + END) AS jul_net, + Sum(CASE + WHEN d_moy = 8 THEN cs_net_paid * cs_quantity + ELSE 0 + END) AS aug_net, + Sum(CASE + WHEN d_moy = 9 THEN cs_net_paid * cs_quantity + ELSE 0 + END) AS sep_net, + Sum(CASE + WHEN d_moy = 10 THEN cs_net_paid * cs_quantity + ELSE 0 + END) AS oct_net, + Sum(CASE + WHEN d_moy = 11 THEN cs_net_paid * cs_quantity + ELSE 0 + END) AS nov_net, + Sum(CASE + WHEN d_moy = 12 THEN cs_net_paid * cs_quantity + ELSE 0 + END) AS dec_net + FROM catalog_sales, + warehouse, + date_dim, + time_dim, + ship_mode + WHERE cs_warehouse_sk = w_warehouse_sk + AND cs_sold_date_sk = d_date_sk + AND cs_sold_time_sk = t_time_sk + AND cs_ship_mode_sk = sm_ship_mode_sk + AND d_year = 1998 + AND t_time BETWEEN 7249 AND 7249 + 28800 + AND sm_carrier IN ( 'ZOUROS', 'ZHOU' ) + GROUP BY w_warehouse_name, + w_warehouse_sq_ft, + w_city, + w_county, + w_state, + w_country, + d_year) x +GROUP BY w_warehouse_name, + w_warehouse_sq_ft, + w_city, + w_county, + w_state, + w_country, + ship_carriers, + year1 +ORDER BY w_warehouse_name +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query67.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query67.sql new file mode 100644 index 000000000..18c39edb5 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query67.sql @@ -0,0 +1,43 @@ +-- start query 67 in stream 0 using template query67.tpl +select * +from (select i_category + ,i_class + ,i_brand + ,i_product_name + ,d_year + ,d_qoy + ,d_moy + ,s_store_id + ,sumsales + ,rank() over (partition by i_category order by sumsales desc) rk + from (select i_category + ,i_class + ,i_brand + ,i_product_name + ,d_year + ,d_qoy + ,d_moy + ,s_store_id + ,sum(coalesce(ss_sales_price*ss_quantity,0)) sumsales + from store_sales + ,date_dim + ,store + ,item + where ss_sold_date_sk=d_date_sk + and ss_item_sk=i_item_sk + and ss_store_sk = s_store_sk + and d_month_seq between 1181 and 1181+11 + group by rollup(i_category, i_class, i_brand, i_product_name, d_year, d_qoy, d_moy,s_store_id))dw1) dw2 +where rk <= 100 +order by i_category + ,i_class + ,i_brand + ,i_product_name + ,d_year + ,d_qoy + ,d_moy + ,s_store_id + ,sumsales + ,rk +limit 100 +; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query68.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query68.sql new file mode 100644 index 000000000..f943603f8 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query68.sql @@ -0,0 +1,41 @@ +-- start query 68 in stream 0 using template query68.tpl +SELECT c_last_name, + c_first_name, + ca_city, + bought_city, + ss_ticket_number, + extended_price, + extended_tax, + list_price +FROM (SELECT ss_ticket_number, + ss_customer_sk, + ca_city bought_city, + Sum(ss_ext_sales_price) extended_price, + Sum(ss_ext_list_price) list_price, + Sum(ss_ext_tax) extended_tax + FROM store_sales, + date_dim, + store, + household_demographics, + customer_address + WHERE store_sales.ss_sold_date_sk = date_dim.d_date_sk + AND store_sales.ss_store_sk = store.s_store_sk + AND store_sales.ss_hdemo_sk = household_demographics.hd_demo_sk + AND store_sales.ss_addr_sk = customer_address.ca_address_sk + AND date_dim.d_dom BETWEEN 1 AND 2 + AND ( household_demographics.hd_dep_count = 8 + OR household_demographics.hd_vehicle_count = 3 ) + AND date_dim.d_year IN ( 1998, 1998 + 1, 1998 + 2 ) + AND store.s_city IN ( 'Fairview', 'Midway' ) + GROUP BY ss_ticket_number, + ss_customer_sk, + ss_addr_sk, + ca_city) dn, + customer, + customer_address current_addr +WHERE ss_customer_sk = c_customer_sk + AND customer.c_current_addr_sk = current_addr.ca_address_sk + AND current_addr.ca_city <> bought_city +ORDER BY c_last_name, + ss_ticket_number +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query69.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query69.sql new file mode 100644 index 000000000..e22094bd8 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query69.sql @@ -0,0 +1,46 @@ +SELECT cd_gender, + cd_marital_status, + cd_education_status, + Count(*) cnt1, + cd_purchase_estimate, + Count(*) cnt2, + cd_credit_rating, + Count(*) cnt3 +FROM customer c, + customer_address ca, + customer_demographics +WHERE c.c_current_addr_sk = ca.ca_address_sk + AND ca_state IN ( 'KS', 'AZ', 'NE' ) + AND cd_demo_sk = c.c_current_cdemo_sk + AND EXISTS (SELECT * + FROM store_sales, + date_dim + WHERE c.c_customer_sk = ss_customer_sk + AND ss_sold_date_sk = d_date_sk + AND d_year = 2004 + AND d_moy BETWEEN 3 AND 3 + 2) + AND ( NOT EXISTS (SELECT * + FROM web_sales, + date_dim + WHERE c.c_customer_sk = ws_bill_customer_sk + AND ws_sold_date_sk = d_date_sk + AND d_year = 2004 + AND d_moy BETWEEN 3 AND 3 + 2) + AND NOT EXISTS (SELECT * + FROM catalog_sales, + date_dim + WHERE c.c_customer_sk = cs_ship_customer_sk + AND cs_sold_date_sk = d_date_sk + AND d_year = 2004 + AND d_moy BETWEEN 3 AND 3 + 2) ) +GROUP BY cd_gender, + cd_marital_status, + cd_education_status, + cd_purchase_estimate, + cd_credit_rating +ORDER BY cd_gender, + cd_marital_status, + cd_education_status, + cd_purchase_estimate, + cd_credit_rating +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query7.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query7.sql new file mode 100644 index 000000000..6e28b46c8 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query7.sql @@ -0,0 +1,24 @@ +-- start query 7 in stream 0 using template query7.tpl +SELECT i_item_id, + Avg(ss_quantity) agg1, + Avg(ss_list_price) agg2, + Avg(ss_coupon_amt) agg3, + Avg(ss_sales_price) agg4 +FROM store_sales, + customer_demographics, + date_dim, + item, + promotion +WHERE ss_sold_date_sk = d_date_sk + AND ss_item_sk = i_item_sk + AND ss_cdemo_sk = cd_demo_sk + AND ss_promo_sk = p_promo_sk + AND cd_gender = 'F' + AND cd_marital_status = 'W' + AND cd_education_status = '2 yr Degree' + AND ( p_channel_email = 'N' + OR p_channel_event = 'N' ) + AND d_year = 1998 +GROUP BY i_item_id +ORDER BY i_item_id +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query70.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query70.sql new file mode 100644 index 000000000..34c5e0656 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query70.sql @@ -0,0 +1,40 @@ +-- start query 70 in stream 0 using template query70.tpl +SELECT Sum(ss_net_profit) AS total_sum, + s_state, + s_county, + Grouping(s_state) + Grouping(s_county) AS lochierarchy, + Rank() + OVER ( + partition BY Grouping(s_state)+Grouping(s_county), CASE WHEN + Grouping( + s_county) = 0 THEN s_state END + ORDER BY Sum(ss_net_profit) DESC) AS rank_within_parent +FROM store_sales, + date_dim d1, + store +WHERE d1.d_month_seq BETWEEN 1200 AND 1200 + 11 + AND d1.d_date_sk = ss_sold_date_sk + AND s_store_sk = ss_store_sk + AND s_state IN (SELECT s_state + FROM (SELECT s_state AS + s_state, + Rank() + OVER ( + partition BY s_state + ORDER BY Sum(ss_net_profit) DESC) AS + ranking + FROM store_sales, + store, + date_dim + WHERE d_month_seq BETWEEN 1200 AND 1200 + 11 + AND d_date_sk = ss_sold_date_sk + AND s_store_sk = ss_store_sk + GROUP BY s_state) tmp1 + WHERE ranking <= 5) +GROUP BY rollup( s_state, s_county ) +ORDER BY lochierarchy DESC, + CASE + WHEN lochierarchy = 0 THEN s_state + END, + rank_within_parent +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query71.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query71.sql new file mode 100644 index 000000000..42075471e --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query71.sql @@ -0,0 +1,48 @@ +-- start query 71 in stream 0 using template query71.tpl +SELECT i_brand_id brand_id, + i_brand brand, + t_hour, + t_minute, + Sum(ext_price) ext_price +FROM item, + (SELECT ws_ext_sales_price AS ext_price, + ws_sold_date_sk AS sold_date_sk, + ws_item_sk AS sold_item_sk, + ws_sold_time_sk AS time_sk + FROM web_sales, + date_dim + WHERE d_date_sk = ws_sold_date_sk + AND d_moy = 11 + AND d_year = 2001 + UNION ALL + SELECT cs_ext_sales_price AS ext_price, + cs_sold_date_sk AS sold_date_sk, + cs_item_sk AS sold_item_sk, + cs_sold_time_sk AS time_sk + FROM catalog_sales, + date_dim + WHERE d_date_sk = cs_sold_date_sk + AND d_moy = 11 + AND d_year = 2001 + UNION ALL + SELECT ss_ext_sales_price AS ext_price, + ss_sold_date_sk AS sold_date_sk, + ss_item_sk AS sold_item_sk, + ss_sold_time_sk AS time_sk + FROM store_sales, + date_dim + WHERE d_date_sk = ss_sold_date_sk + AND d_moy = 11 + AND d_year = 2001) AS tmp, + time_dim +WHERE sold_item_sk = i_item_sk + AND i_manager_id = 1 + AND time_sk = t_time_sk + AND ( t_meal_time = 'breakfast' + OR t_meal_time = 'dinner' ) +GROUP BY i_brand, + i_brand_id, + t_hour, + t_minute +ORDER BY ext_price DESC, + i_brand_id; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query72.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query72.sql new file mode 100644 index 000000000..e0e082cb4 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query72.sql @@ -0,0 +1,49 @@ +-- start query 72 in stream 0 using template query72.tpl +SELECT i_item_desc, + w_warehouse_name, + d1.d_week_seq, + Sum(CASE + WHEN p_promo_sk IS NULL THEN 1 + ELSE 0 + END) no_promo, + Sum(CASE + WHEN p_promo_sk IS NOT NULL THEN 1 + ELSE 0 + END) promo, + Count(*) total_cnt +FROM catalog_sales + JOIN inventory + ON ( cs_item_sk = inv_item_sk ) + JOIN warehouse + ON ( w_warehouse_sk = inv_warehouse_sk ) + JOIN item + ON ( i_item_sk = cs_item_sk ) + JOIN customer_demographics + ON ( cs_bill_cdemo_sk = cd_demo_sk ) + JOIN household_demographics + ON ( cs_bill_hdemo_sk = hd_demo_sk ) + JOIN date_dim d1 + ON ( cs_sold_date_sk = d1.d_date_sk ) + JOIN date_dim d2 + ON ( inv_date_sk = d2.d_date_sk ) + JOIN date_dim d3 + ON ( cs_ship_date_sk = d3.d_date_sk ) + LEFT OUTER JOIN promotion + ON ( cs_promo_sk = p_promo_sk ) + LEFT OUTER JOIN catalog_returns + ON ( cr_item_sk = cs_item_sk + AND cr_order_number = cs_order_number ) +WHERE d1.d_week_seq = d2.d_week_seq + AND inv_quantity_on_hand < cs_quantity + AND d3.d_date > d1.d_date + INTERVAL '5' day + AND hd_buy_potential = '501-1000' + AND d1.d_year = 2002 + AND cd_marital_status = 'M' +GROUP BY i_item_desc, + w_warehouse_name, + d1.d_week_seq +ORDER BY total_cnt DESC, + i_item_desc, + w_warehouse_name, + d_week_seq +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query73.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query73.sql new file mode 100644 index 000000000..d56d682c3 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query73.sql @@ -0,0 +1,39 @@ +-- start query 73 in stream 0 using template query73.tpl +SELECT c_last_name, + c_first_name, + c_salutation, + c_preferred_cust_flag, + ss_ticket_number, + cnt +FROM (SELECT ss_ticket_number, + ss_customer_sk, + Count(*) cnt + FROM store_sales, + date_dim, + store, + household_demographics + WHERE store_sales.ss_sold_date_sk = date_dim.d_date_sk + AND store_sales.ss_store_sk = store.s_store_sk + AND store_sales.ss_hdemo_sk = household_demographics.hd_demo_sk + AND date_dim.d_dom BETWEEN 1 AND 2 + AND ( household_demographics.hd_buy_potential = '>10000' + OR household_demographics.hd_buy_potential = '0-500' ) + AND household_demographics.hd_vehicle_count > 0 + AND CASE + WHEN household_demographics.hd_vehicle_count > 0 THEN + household_demographics.hd_dep_count / + household_demographics.hd_vehicle_count + ELSE NULL + END > 1 + AND date_dim.d_year IN ( 2000, 2000 + 1, 2000 + 2 ) + AND store.s_county IN ( 'Williamson County', 'Williamson County', + 'Williamson County', + 'Williamson County' + ) + GROUP BY ss_ticket_number, + ss_customer_sk) dj, + customer +WHERE ss_customer_sk = c_customer_sk + AND cnt BETWEEN 1 AND 5 +ORDER BY cnt DESC, + c_last_name ASC; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query74.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query74.sql new file mode 100644 index 000000000..f8d781f79 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query74.sql @@ -0,0 +1,69 @@ +-- start query 74 in stream 0 using template query74.tpl +WITH year_total + AS (SELECT c_customer_id customer_id, + c_first_name customer_first_name, + c_last_name customer_last_name, + d_year AS year1, + Sum(ss_net_paid) year_total, + 's' sale_type + FROM customer, + store_sales, + date_dim + WHERE c_customer_sk = ss_customer_sk + AND ss_sold_date_sk = d_date_sk + AND d_year IN ( 1999, 1999 + 1 ) + GROUP BY c_customer_id, + c_first_name, + c_last_name, + d_year + UNION ALL + SELECT c_customer_id customer_id, + c_first_name customer_first_name, + c_last_name customer_last_name, + d_year AS year1, + Sum(ws_net_paid) year_total, + 'w' sale_type + FROM customer, + web_sales, + date_dim + WHERE c_customer_sk = ws_bill_customer_sk + AND ws_sold_date_sk = d_date_sk + AND d_year IN ( 1999, 1999 + 1 ) + GROUP BY c_customer_id, + c_first_name, + c_last_name, + d_year) +SELECT t_s_secyear.customer_id, + t_s_secyear.customer_first_name, + t_s_secyear.customer_last_name +FROM year_total t_s_firstyear, + year_total t_s_secyear, + year_total t_w_firstyear, + year_total t_w_secyear +WHERE t_s_secyear.customer_id = t_s_firstyear.customer_id + AND t_s_firstyear.customer_id = t_w_secyear.customer_id + AND t_s_firstyear.customer_id = t_w_firstyear.customer_id + AND t_s_firstyear.sale_type = 's' + AND t_w_firstyear.sale_type = 'w' + AND t_s_secyear.sale_type = 's' + AND t_w_secyear.sale_type = 'w' + AND t_s_firstyear.year1 = 1999 + AND t_s_secyear.year1 = 1999 + 1 + AND t_w_firstyear.year1 = 1999 + AND t_w_secyear.year1 = 1999 + 1 + AND t_s_firstyear.year_total > 0 + AND t_w_firstyear.year_total > 0 + AND CASE + WHEN t_w_firstyear.year_total > 0 THEN t_w_secyear.year_total / + t_w_firstyear.year_total + ELSE NULL + END > CASE + WHEN t_s_firstyear.year_total > 0 THEN + t_s_secyear.year_total / + t_s_firstyear.year_total + ELSE NULL + END +ORDER BY 1, + 2, + 3 +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query75.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query75.sql new file mode 100644 index 000000000..25d1ccbd4 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query75.sql @@ -0,0 +1,93 @@ +-- start query 75 in stream 0 using template query75.tpl +WITH all_sales + AS (SELECT d_year, + i_brand_id, + i_class_id, + i_category_id, + i_manufact_id, + Sum(sales_cnt) AS sales_cnt, + Sum(sales_amt) AS sales_amt + FROM (SELECT d_year, + i_brand_id, + i_class_id, + i_category_id, + i_manufact_id, + cs_quantity - COALESCE(cr_return_quantity, 0) AS + sales_cnt, + cs_ext_sales_price - COALESCE(cr_return_amount, 0.0) AS + sales_amt + FROM catalog_sales + JOIN item + ON i_item_sk = cs_item_sk + JOIN date_dim + ON d_date_sk = cs_sold_date_sk + LEFT JOIN catalog_returns + ON ( cs_order_number = cr_order_number + AND cs_item_sk = cr_item_sk ) + WHERE i_category = 'Men' + UNION + SELECT d_year, + i_brand_id, + i_class_id, + i_category_id, + i_manufact_id, + ss_quantity - COALESCE(sr_return_quantity, 0) AS + sales_cnt, + ss_ext_sales_price - COALESCE(sr_return_amt, 0.0) AS + sales_amt + FROM store_sales + JOIN item + ON i_item_sk = ss_item_sk + JOIN date_dim + ON d_date_sk = ss_sold_date_sk + LEFT JOIN store_returns + ON ( ss_ticket_number = sr_ticket_number + AND ss_item_sk = sr_item_sk ) + WHERE i_category = 'Men' + UNION + SELECT d_year, + i_brand_id, + i_class_id, + i_category_id, + i_manufact_id, + ws_quantity - COALESCE(wr_return_quantity, 0) AS + sales_cnt, + ws_ext_sales_price - COALESCE(wr_return_amt, 0.0) AS + sales_amt + FROM web_sales + JOIN item + ON i_item_sk = ws_item_sk + JOIN date_dim + ON d_date_sk = ws_sold_date_sk + LEFT JOIN web_returns + ON ( ws_order_number = wr_order_number + AND ws_item_sk = wr_item_sk ) + WHERE i_category = 'Men') sales_detail + GROUP BY d_year, + i_brand_id, + i_class_id, + i_category_id, + i_manufact_id) +SELECT prev_yr.d_year AS prev_year, + curr_yr.d_year AS year1, + curr_yr.i_brand_id, + curr_yr.i_class_id, + curr_yr.i_category_id, + curr_yr.i_manufact_id, + prev_yr.sales_cnt AS prev_yr_cnt, + curr_yr.sales_cnt AS curr_yr_cnt, + curr_yr.sales_cnt - prev_yr.sales_cnt AS sales_cnt_diff, + curr_yr.sales_amt - prev_yr.sales_amt AS sales_amt_diff +FROM all_sales curr_yr, + all_sales prev_yr +WHERE curr_yr.i_brand_id = prev_yr.i_brand_id + AND curr_yr.i_class_id = prev_yr.i_class_id + AND curr_yr.i_category_id = prev_yr.i_category_id + AND curr_yr.i_manufact_id = prev_yr.i_manufact_id + AND curr_yr.d_year = 2002 + AND prev_yr.d_year = 2002 - 1 + AND Cast(curr_yr.sales_cnt AS DECIMAL(17, 2)) / Cast(prev_yr.sales_cnt AS + DECIMAL(17, 2)) + < 0.9 +ORDER BY sales_cnt_diff +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query76.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query76.sql new file mode 100644 index 000000000..f298aa015 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query76.sql @@ -0,0 +1,57 @@ +-- start query 76 in stream 0 using template query76.tpl +SELECT channel, + col_name, + d_year, + d_qoy, + i_category, + Count(*) sales_cnt, + Sum(ext_sales_price) sales_amt +FROM (SELECT 'store' AS channel, + 'ss_hdemo_sk' col_name, + d_year, + d_qoy, + i_category, + ss_ext_sales_price ext_sales_price + FROM store_sales, + item, + date_dim + WHERE ss_hdemo_sk IS NULL + AND ss_sold_date_sk = d_date_sk + AND ss_item_sk = i_item_sk + UNION ALL + SELECT 'web' AS channel, + 'ws_ship_hdemo_sk' col_name, + d_year, + d_qoy, + i_category, + ws_ext_sales_price ext_sales_price + FROM web_sales, + item, + date_dim + WHERE ws_ship_hdemo_sk IS NULL + AND ws_sold_date_sk = d_date_sk + AND ws_item_sk = i_item_sk + UNION ALL + SELECT 'catalog' AS channel, + 'cs_warehouse_sk' col_name, + d_year, + d_qoy, + i_category, + cs_ext_sales_price ext_sales_price + FROM catalog_sales, + item, + date_dim + WHERE cs_warehouse_sk IS NULL + AND cs_sold_date_sk = d_date_sk + AND cs_item_sk = i_item_sk) foo +GROUP BY channel, + col_name, + d_year, + d_qoy, + i_category +ORDER BY channel, + col_name, + d_year, + d_qoy, + i_category +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query77.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query77.sql new file mode 100644 index 000000000..ebcea2b12 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query77.sql @@ -0,0 +1,107 @@ + +-- start query 77 in stream 0 using template query77.tpl +WITH ss AS +( + SELECT s_store_sk, + Sum(ss_ext_sales_price) AS sales, + Sum(ss_net_profit) AS profit + FROM store_sales, + date_dim, + store + WHERE ss_sold_date_sk = d_date_sk + AND d_date BETWEEN Cast('2001-08-16' AS DATE) AND ( + Cast('2001-08-16' AS DATE) + INTERVAL '30' day) + AND ss_store_sk = s_store_sk + GROUP BY s_store_sk) , sr AS +( + SELECT s_store_sk, + sum(sr_return_amt) AS returns1, + sum(sr_net_loss) AS profit_loss + FROM store_returns, + date_dim, + store + WHERE sr_returned_date_sk = d_date_sk + AND d_date BETWEEN cast('2001-08-16' AS date) AND ( + cast('2001-08-16' AS date) + INTERVAL '30' day) + AND sr_store_sk = s_store_sk + GROUP BY s_store_sk), cs AS +( + SELECT cs_call_center_sk, + sum(cs_ext_sales_price) AS sales, + sum(cs_net_profit) AS profit + FROM catalog_sales, + date_dim + WHERE cs_sold_date_sk = d_date_sk + AND d_date BETWEEN cast('2001-08-16' AS date) AND ( + cast('2001-08-16' AS date) + INTERVAL '30' day) + GROUP BY cs_call_center_sk ), cr AS +( + SELECT cr_call_center_sk, + sum(cr_return_amount) AS returns1, + sum(cr_net_loss) AS profit_loss + FROM catalog_returns, + date_dim + WHERE cr_returned_date_sk = d_date_sk + AND d_date BETWEEN cast('2001-08-16' AS date) AND ( + cast('2001-08-16' AS date) + INTERVAL '30' day) + GROUP BY cr_call_center_sk ), ws AS +( + SELECT wp_web_page_sk, + sum(ws_ext_sales_price) AS sales, + sum(ws_net_profit) AS profit + FROM web_sales, + date_dim, + web_page + WHERE ws_sold_date_sk = d_date_sk + AND d_date BETWEEN cast('2001-08-16' AS date) AND ( + cast('2001-08-16' AS date) + INTERVAL '30' day) + AND ws_web_page_sk = wp_web_page_sk + GROUP BY wp_web_page_sk), wr AS +( + SELECT wp_web_page_sk, + sum(wr_return_amt) AS returns1, + sum(wr_net_loss) AS profit_loss + FROM web_returns, + date_dim, + web_page + WHERE wr_returned_date_sk = d_date_sk + AND d_date BETWEEN cast('2001-08-16' AS date) AND ( + cast('2001-08-16' AS date) + INTERVAL '30' day) + AND wr_web_page_sk = wp_web_page_sk + GROUP BY wp_web_page_sk) +SELECT + channel , + id , + sum(sales) AS sales , + sum(returns1) AS returns1 , + sum(profit) AS profit +FROM ( + SELECT 'store channel' AS channel , + ss.s_store_sk AS id , + sales , + COALESCE(returns1, 0) AS returns1 , + (profit - COALESCE(profit_loss,0)) AS profit + FROM ss + LEFT JOIN sr + ON ss.s_store_sk = sr.s_store_sk + UNION ALL + SELECT 'catalog channel' AS channel , + cs_call_center_sk AS id , + sales , + returns1 , + (profit - profit_loss) AS profit + FROM cs , + cr + UNION ALL + SELECT 'web channel' AS channel , + ws.wp_web_page_sk AS id , + sales , + COALESCE(returns1, 0) returns1 , + (profit - COALESCE(profit_loss,0)) AS profit + FROM ws + LEFT JOIN wr + ON ws.wp_web_page_sk = wr.wp_web_page_sk ) x +GROUP BY rollup (channel, id) +ORDER BY channel , + id +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query78.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query78.sql new file mode 100644 index 000000000..b57b52389 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query78.sql @@ -0,0 +1,86 @@ +-- start query 78 in stream 0 using template query78.tpl +WITH ws + AS (SELECT d_year AS ws_sold_year, + ws_item_sk, + ws_bill_customer_sk ws_customer_sk, + Sum(ws_quantity) ws_qty, + Sum(ws_wholesale_cost) ws_wc, + Sum(ws_sales_price) ws_sp + FROM web_sales + LEFT JOIN web_returns + ON wr_order_number = ws_order_number + AND ws_item_sk = wr_item_sk + JOIN date_dim + ON ws_sold_date_sk = d_date_sk + WHERE wr_order_number IS NULL + GROUP BY d_year, + ws_item_sk, + ws_bill_customer_sk), + cs + AS (SELECT d_year AS cs_sold_year, + cs_item_sk, + cs_bill_customer_sk cs_customer_sk, + Sum(cs_quantity) cs_qty, + Sum(cs_wholesale_cost) cs_wc, + Sum(cs_sales_price) cs_sp + FROM catalog_sales + LEFT JOIN catalog_returns + ON cr_order_number = cs_order_number + AND cs_item_sk = cr_item_sk + JOIN date_dim + ON cs_sold_date_sk = d_date_sk + WHERE cr_order_number IS NULL + GROUP BY d_year, + cs_item_sk, + cs_bill_customer_sk), + ss + AS (SELECT d_year AS ss_sold_year, + ss_item_sk, + ss_customer_sk, + Sum(ss_quantity) ss_qty, + Sum(ss_wholesale_cost) ss_wc, + Sum(ss_sales_price) ss_sp + FROM store_sales + LEFT JOIN store_returns + ON sr_ticket_number = ss_ticket_number + AND ss_item_sk = sr_item_sk + JOIN date_dim + ON ss_sold_date_sk = d_date_sk + WHERE sr_ticket_number IS NULL + GROUP BY d_year, + ss_item_sk, + ss_customer_sk) +SELECT ss_item_sk, + Round(ss_qty / ( COALESCE(ws_qty + cs_qty, 1) ), 2) ratio, + ss_qty store_qty, + ss_wc + store_wholesale_cost, + ss_sp + store_sales_price, + COALESCE(ws_qty, 0) + COALESCE(cs_qty, 0) + other_chan_qty, + COALESCE(ws_wc, 0) + COALESCE(cs_wc, 0) + other_chan_wholesale_cost, + COALESCE(ws_sp, 0) + COALESCE(cs_sp, 0) + other_chan_sales_price +FROM ss + LEFT JOIN ws + ON ( ws_sold_year = ss_sold_year + AND ws_item_sk = ss_item_sk + AND ws_customer_sk = ss_customer_sk ) + LEFT JOIN cs + ON ( cs_sold_year = ss_sold_year + AND cs_item_sk = cs_item_sk + AND cs_customer_sk = ss_customer_sk ) +WHERE COALESCE(ws_qty, 0) > 0 + AND COALESCE(cs_qty, 0) > 0 + AND ss_sold_year = 1999 +ORDER BY ss_item_sk, + ss_qty DESC, + ss_wc DESC, + ss_sp DESC, + other_chan_qty, + other_chan_wholesale_cost, + other_chan_sales_price, + Round(ss_qty / ( COALESCE(ws_qty + cs_qty, 1) ), 2) +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query79.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query79.sql new file mode 100644 index 000000000..7e8f52d09 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query79.sql @@ -0,0 +1,35 @@ +-- start query 79 in stream 0 using template query79.tpl +SELECT c_last_name, + c_first_name, + Substr(s_city, 1, 30), + ss_ticket_number, + amt, + profit +FROM (SELECT ss_ticket_number, + ss_customer_sk, + store.s_city, + Sum(ss_coupon_amt) amt, + Sum(ss_net_profit) profit + FROM store_sales, + date_dim, + store, + household_demographics + WHERE store_sales.ss_sold_date_sk = date_dim.d_date_sk + AND store_sales.ss_store_sk = store.s_store_sk + AND store_sales.ss_hdemo_sk = household_demographics.hd_demo_sk + AND ( household_demographics.hd_dep_count = 8 + OR household_demographics.hd_vehicle_count > 4 ) + AND date_dim.d_dow = 1 + AND date_dim.d_year IN ( 2000, 2000 + 1, 2000 + 2 ) + AND store.s_number_employees BETWEEN 200 AND 295 + GROUP BY ss_ticket_number, + ss_customer_sk, + ss_addr_sk, + store.s_city) ms, + customer +WHERE ss_customer_sk = c_customer_sk +ORDER BY c_last_name, + c_first_name, + Substr(s_city, 1, 30), + profit +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query8.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query8.sql new file mode 100644 index 000000000..8d98a76a4 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query8.sql @@ -0,0 +1,227 @@ +-- start query 8 in stream 0 using template query8.tpl +SELECT s_store_name, + Sum(ss_net_profit) +FROM store_sales, + date_dim, + store, + (SELECT ca_zip + FROM (SELECT Substr(ca_zip, 1, 5) ca_zip + FROM customer_address + WHERE Substr(ca_zip, 1, 5) IN ( '67436', '26121', '38443', + '63157', + '68856', '19485', '86425', + '26741', + '70991', '60899', '63573', + '47556', + '56193', '93314', '87827', + '62017', + '85067', '95390', '48091', + '10261', + '81845', '41790', '42853', + '24675', + '12840', '60065', '84430', + '57451', + '24021', '91735', '75335', + '71935', + '34482', '56943', '70695', + '52147', + '56251', '28411', '86653', + '23005', + '22478', '29031', '34398', + '15365', + '42460', '33337', '59433', + '73943', + '72477', '74081', '74430', + '64605', + '39006', '11226', '49057', + '97308', + '42663', '18187', '19768', + '43454', + '32147', '76637', '51975', + '11181', + '45630', '33129', '45995', + '64386', + '55522', '26697', '20963', + '35154', + '64587', '49752', '66386', + '30586', + '59286', '13177', '66646', + '84195', + '74316', '36853', '32927', + '12469', + '11904', '36269', '17724', + '55346', + '12595', '53988', '65439', + '28015', + '63268', '73590', '29216', + '82575', + '69267', '13805', '91678', + '79460', + '94152', '14961', '15419', + '48277', + '62588', '55493', '28360', + '14152', + '55225', '18007', '53705', + '56573', + '80245', '71769', '57348', + '36845', + '13039', '17270', '22363', + '83474', + '25294', '43269', '77666', + '15488', + '99146', '64441', '43338', + '38736', + '62754', '48556', '86057', + '23090', + '38114', '66061', '18910', + '84385', + '23600', '19975', '27883', + '65719', + '19933', '32085', '49731', + '40473', + '27190', '46192', '23949', + '44738', + '12436', '64794', '68741', + '15333', + '24282', '49085', '31844', + '71156', + '48441', '17100', '98207', + '44982', + '20277', '71496', '96299', + '37583', + '22206', '89174', '30589', + '61924', + '53079', '10976', '13104', + '42794', + '54772', '15809', '56434', + '39975', + '13874', '30753', '77598', + '78229', + '59478', '12345', '55547', + '57422', + '42600', '79444', '29074', + '29752', + '21676', '32096', '43044', + '39383', + '37296', '36295', '63077', + '16572', + '31275', '18701', '40197', + '48242', + '27219', '49865', '84175', + '30446', + '25165', '13807', '72142', + '70499', + '70464', '71429', '18111', + '70857', + '29545', '36425', '52706', + '36194', + '42963', '75068', '47921', + '74763', + '90990', '89456', '62073', + '88397', + '73963', '75885', '62657', + '12530', + '81146', '57434', '25099', + '41429', + '98441', '48713', '52552', + '31667', + '14072', '13903', '44709', + '85429', + '58017', '38295', '44875', + '73541', + '30091', '12707', '23762', + '62258', + '33247', '78722', '77431', + '14510', + '35656', '72428', '92082', + '35267', + '43759', '24354', '90952', + '11512', + '21242', '22579', '56114', + '32339', + '52282', '41791', '24484', + '95020', + '28408', '99710', '11899', + '43344', + '72915', '27644', '62708', + '74479', + '17177', '32619', '12351', + '91339', + '31169', '57081', '53522', + '16712', + '34419', '71779', '44187', + '46206', + '96099', '61910', '53664', + '12295', + '31837', '33096', '10813', + '63048', + '31732', '79118', '73084', + '72783', + '84952', '46965', '77956', + '39815', + '32311', '75329', '48156', + '30826', + '49661', '13736', '92076', + '74865', + '88149', '92397', '52777', + '68453', + '32012', '21222', '52721', + '24626', + '18210', '42177', '91791', + '75251', + '82075', '44372', '45542', + '20609', + '60115', '17362', '22750', + '90434', + '31852', '54071', '33762', + '14705', + '40718', '56433', '30996', + '40657', + '49056', '23585', '66455', + '41021', + '74736', '72151', '37007', + '21729', + '60177', '84558', '59027', + '93855', + '60022', '86443', '19541', + '86886', + '30532', '39062', '48532', + '34713', + '52077', '22564', '64638', + '15273', + '31677', '36138', '62367', + '60261', + '80213', '42818', '25113', + '72378', + '69802', '69096', '55443', + '28820', + '13848', '78258', '37490', + '30556', + '77380', '28447', '44550', + '26791', + '70609', '82182', '33306', + '43224', + '22322', '86959', '68519', + '14308', + '46501', '81131', '34056', + '61991', + '19896', '87804', '65774', + '92564' ) + INTERSECT + SELECT ca_zip + FROM (SELECT Substr(ca_zip, 1, 5) ca_zip, + Count(*) cnt + FROM customer_address, + customer + WHERE ca_address_sk = c_current_addr_sk + AND c_preferred_cust_flag = 'Y' + GROUP BY ca_zip + HAVING Count(*) > 10)A1)A2) V1 +WHERE ss_store_sk = s_store_sk + AND ss_sold_date_sk = d_date_sk + AND d_qoy = 2 + AND d_year = 2000 + AND ( Substr(s_zip, 1, 2) = Substr(V1.ca_zip, 1, 2) ) +GROUP BY s_store_name +ORDER BY s_store_name +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query80.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query80.sql new file mode 100644 index 000000000..4fcb9cb2f --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query80.sql @@ -0,0 +1,105 @@ +-- start query 80 in stream 0 using template query80.tpl +WITH ssr AS +( + SELECT s_store_id AS store_id, + Sum(ss_ext_sales_price) AS sales, + Sum(COALESCE(sr_return_amt, 0)) AS returns1, + Sum(ss_net_profit - COALESCE(sr_net_loss, 0)) AS profit + FROM store_sales + LEFT OUTER JOIN store_returns + ON ( + ss_item_sk = sr_item_sk + AND ss_ticket_number = sr_ticket_number), + date_dim, + store, + item, + promotion + WHERE ss_sold_date_sk = d_date_sk + AND d_date BETWEEN Cast('2000-08-26' AS DATE) AND ( + Cast('2000-08-26' AS DATE) + INTERVAL '30' day) + AND ss_store_sk = s_store_sk + AND ss_item_sk = i_item_sk + AND i_current_price > 50 + AND ss_promo_sk = p_promo_sk + AND p_channel_tv = 'N' + GROUP BY s_store_id) , csr AS +( + SELECT cp_catalog_page_id AS catalog_page_id, + sum(cs_ext_sales_price) AS sales, + sum(COALESCE(cr_return_amount, 0)) AS returns1, + sum(cs_net_profit - COALESCE(cr_net_loss, 0)) AS profit + FROM catalog_sales + LEFT OUTER JOIN catalog_returns + ON ( + cs_item_sk = cr_item_sk + AND cs_order_number = cr_order_number), + date_dim, + catalog_page, + item, + promotion + WHERE cs_sold_date_sk = d_date_sk + AND d_date BETWEEN cast('2000-08-26' AS date) AND ( + cast('2000-08-26' AS date) + INTERVAL '30' day) + AND cs_catalog_page_sk = cp_catalog_page_sk + AND cs_item_sk = i_item_sk + AND i_current_price > 50 + AND cs_promo_sk = p_promo_sk + AND p_channel_tv = 'N' + GROUP BY cp_catalog_page_id) , wsr AS +( + SELECT web_site_id, + sum(ws_ext_sales_price) AS sales, + sum(COALESCE(wr_return_amt, 0)) AS returns1, + sum(ws_net_profit - COALESCE(wr_net_loss, 0)) AS profit + FROM web_sales + LEFT OUTER JOIN web_returns + ON ( + ws_item_sk = wr_item_sk + AND ws_order_number = wr_order_number), + date_dim, + web_site, + item, + promotion + WHERE ws_sold_date_sk = d_date_sk + AND d_date BETWEEN cast('2000-08-26' AS date) AND ( + cast('2000-08-26' AS date) + INTERVAL '30' day) + AND ws_web_site_sk = web_site_sk + AND ws_item_sk = i_item_sk + AND i_current_price > 50 + AND ws_promo_sk = p_promo_sk + AND p_channel_tv = 'N' + GROUP BY web_site_id) +SELECT + channel , + id , + sum(sales) AS sales , + sum(returns1) AS returns1 , + sum(profit) AS profit +FROM ( + SELECT 'store channel' AS channel , + 'store' + || store_id AS id , + sales , + returns1 , + profit + FROM ssr + UNION ALL + SELECT 'catalog channel' AS channel , + 'catalog_page' + || catalog_page_id AS id , + sales , + returns1 , + profit + FROM csr + UNION ALL + SELECT 'web channel' AS channel , + 'web_site' + || web_site_id AS id , + sales , + returns1 , + profit + FROM wsr ) x +GROUP BY rollup (channel, id) +ORDER BY channel , + id +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query81.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query81.sql new file mode 100644 index 000000000..9be95c5d2 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query81.sql @@ -0,0 +1,56 @@ + +-- start query 81 in stream 0 using template query81.tpl +WITH customer_total_return + AS (SELECT cr_returning_customer_sk AS ctr_customer_sk, + ca_state AS ctr_state, + Sum(cr_return_amt_inc_tax) AS ctr_total_return + FROM catalog_returns, + date_dim, + customer_address + WHERE cr_returned_date_sk = d_date_sk + AND d_year = 1999 + AND cr_returning_addr_sk = ca_address_sk + GROUP BY cr_returning_customer_sk, + ca_state) +SELECT c_customer_id, + c_salutation, + c_first_name, + c_last_name, + ca_street_number, + ca_street_name, + ca_street_type, + ca_suite_number, + ca_city, + ca_county, + ca_state, + ca_zip, + ca_country, + ca_gmt_offset, + ca_location_type, + ctr_total_return +FROM customer_total_return ctr1, + customer_address, + customer +WHERE ctr1.ctr_total_return > (SELECT Avg(ctr_total_return) * 1.2 + FROM customer_total_return ctr2 + WHERE ctr1.ctr_state = ctr2.ctr_state) + AND ca_address_sk = c_current_addr_sk + AND ca_state = 'TX' + AND ctr1.ctr_customer_sk = c_customer_sk +ORDER BY c_customer_id, + c_salutation, + c_first_name, + c_last_name, + ca_street_number, + ca_street_name, + ca_street_type, + ca_suite_number, + ca_city, + ca_county, + ca_state, + ca_zip, + ca_country, + ca_gmt_offset, + ca_location_type, + ctr_total_return +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query82.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query82.sql new file mode 100644 index 000000000..27295a5fb --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query82.sql @@ -0,0 +1,23 @@ + +-- start query 82 in stream 0 using template query82.tpl +SELECT + i_item_id , + i_item_desc , + i_current_price +FROM item, + inventory, + date_dim, + store_sales +WHERE i_current_price BETWEEN 63 AND 63+30 +AND inv_item_sk = i_item_sk +AND d_date_sk=inv_date_sk +AND d_date BETWEEN Cast('1998-04-27' AS DATE) AND ( + Cast('1998-04-27' AS DATE) + INTERVAL '60' day) +AND i_manufact_id IN (57,293,427,320) +AND inv_quantity_on_hand BETWEEN 100 AND 500 +AND ss_item_sk = i_item_sk +GROUP BY i_item_id, + i_item_desc, + i_current_price +ORDER BY i_item_id +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query83.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query83.sql new file mode 100644 index 000000000..b5c4378fa --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query83.sql @@ -0,0 +1,75 @@ +-- start query 83 in stream 0 using template query83.tpl +WITH sr_items + AS (SELECT i_item_id item_id, + Sum(sr_return_quantity) sr_item_qty + FROM store_returns, + item, + date_dim + WHERE sr_item_sk = i_item_sk + AND d_date IN (SELECT d_date + FROM date_dim + WHERE d_week_seq IN (SELECT d_week_seq + FROM date_dim + WHERE + d_date IN ( '1999-06-30', + '1999-08-28', + '1999-11-18' + ))) + AND sr_returned_date_sk = d_date_sk + GROUP BY i_item_id), + cr_items + AS (SELECT i_item_id item_id, + Sum(cr_return_quantity) cr_item_qty + FROM catalog_returns, + item, + date_dim + WHERE cr_item_sk = i_item_sk + AND d_date IN (SELECT d_date + FROM date_dim + WHERE d_week_seq IN (SELECT d_week_seq + FROM date_dim + WHERE + d_date IN ( '1999-06-30', + '1999-08-28', + '1999-11-18' + ))) + AND cr_returned_date_sk = d_date_sk + GROUP BY i_item_id), + wr_items + AS (SELECT i_item_id item_id, + Sum(wr_return_quantity) wr_item_qty + FROM web_returns, + item, + date_dim + WHERE wr_item_sk = i_item_sk + AND d_date IN (SELECT d_date + FROM date_dim + WHERE d_week_seq IN (SELECT d_week_seq + FROM date_dim + WHERE + d_date IN ( '1999-06-30', + '1999-08-28', + '1999-11-18' + ))) + AND wr_returned_date_sk = d_date_sk + GROUP BY i_item_id) +SELECT sr_items.item_id, + sr_item_qty, + sr_item_qty / ( sr_item_qty + cr_item_qty + wr_item_qty ) / 3.0 * + 100 sr_dev, + cr_item_qty, + cr_item_qty / ( sr_item_qty + cr_item_qty + wr_item_qty ) / 3.0 * + 100 cr_dev, + wr_item_qty, + wr_item_qty / ( sr_item_qty + cr_item_qty + wr_item_qty ) / 3.0 * + 100 wr_dev, + ( sr_item_qty + cr_item_qty + wr_item_qty ) / 3.0 + average +FROM sr_items, + cr_items, + wr_items +WHERE sr_items.item_id = cr_items.item_id + AND sr_items.item_id = wr_items.item_id +ORDER BY sr_items.item_id, + sr_item_qty +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query84.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query84.sql new file mode 100644 index 000000000..f7eae1a7e --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query84.sql @@ -0,0 +1,21 @@ +-- start query 84 in stream 0 using template query84.tpl +SELECT c_customer_id AS customer_id, + c_last_name + || ', ' + || c_first_name AS customername +FROM customer, + customer_address, + customer_demographics, + household_demographics, + income_band, + store_returns +WHERE ca_city = 'Green Acres' + AND c_current_addr_sk = ca_address_sk + AND ib_lower_bound >= 54986 + AND ib_upper_bound <= 54986 + 50000 + AND ib_income_band_sk = hd_income_band_sk + AND cd_demo_sk = c_current_cdemo_sk + AND hd_demo_sk = c_current_hdemo_sk + AND sr_cdemo_sk = cd_demo_sk +ORDER BY c_customer_id +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query85.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query85.sql new file mode 100644 index 000000000..be2f68d48 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query85.sql @@ -0,0 +1,52 @@ +-- start query 85 in stream 0 using template query85.tpl +SELECT Substr(r_reason_desc, 1, 20), + Avg(ws_quantity), + Avg(wr_refunded_cash), + Avg(wr_fee) +FROM web_sales, + web_returns, + web_page, + customer_demographics cd1, + customer_demographics cd2, + customer_address, + date_dim, + reason +WHERE ws_web_page_sk = wp_web_page_sk + AND ws_item_sk = wr_item_sk + AND ws_order_number = wr_order_number + AND ws_sold_date_sk = d_date_sk + AND d_year = 2001 + AND cd1.cd_demo_sk = wr_refunded_cdemo_sk + AND cd2.cd_demo_sk = wr_returning_cdemo_sk + AND ca_address_sk = wr_refunded_addr_sk + AND r_reason_sk = wr_reason_sk + AND ( ( cd1.cd_marital_status = 'W' + AND cd1.cd_marital_status = cd2.cd_marital_status + AND cd1.cd_education_status = 'Primary' + AND cd1.cd_education_status = cd2.cd_education_status + AND ws_sales_price BETWEEN 100.00 AND 150.00 ) + OR ( cd1.cd_marital_status = 'D' + AND cd1.cd_marital_status = cd2.cd_marital_status + AND cd1.cd_education_status = 'Secondary' + AND cd1.cd_education_status = cd2.cd_education_status + AND ws_sales_price BETWEEN 50.00 AND 100.00 ) + OR ( cd1.cd_marital_status = 'M' + AND cd1.cd_marital_status = cd2.cd_marital_status + AND cd1.cd_education_status = 'Advanced Degree' + AND cd1.cd_education_status = cd2.cd_education_status + AND ws_sales_price BETWEEN 150.00 AND 200.00 ) ) + AND ( ( ca_country = 'United States' + AND ca_state IN ( 'KY', 'ME', 'IL' ) + AND ws_net_profit BETWEEN 100 AND 200 ) + OR ( ca_country = 'United States' + AND ca_state IN ( 'OK', 'NE', 'MN' ) + AND ws_net_profit BETWEEN 150 AND 300 ) + OR ( ca_country = 'United States' + AND ca_state IN ( 'FL', 'WI', 'KS' ) + AND ws_net_profit BETWEEN 50 AND 250 ) ) +GROUP BY r_reason_desc +ORDER BY Substr(r_reason_desc, 1, 20), + Avg(ws_quantity), + Avg(wr_refunded_cash), + Avg(wr_fee) +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query86.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query86.sql new file mode 100644 index 000000000..ec513d402 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query86.sql @@ -0,0 +1,24 @@ +-- start query 86 in stream 0 using template query86.tpl +SELECT Sum(ws_net_paid) AS total_sum, + i_category, + i_class, + Grouping(i_category) + Grouping(i_class) AS lochierarchy, + Rank() + OVER ( + partition BY Grouping(i_category)+Grouping(i_class), CASE + WHEN Grouping( + i_class) = 0 THEN i_category END + ORDER BY Sum(ws_net_paid) DESC) AS rank_within_parent +FROM web_sales, + date_dim d1, + item +WHERE d1.d_month_seq BETWEEN 1183 AND 1183 + 11 + AND d1.d_date_sk = ws_sold_date_sk + AND i_item_sk = ws_item_sk +GROUP BY rollup( i_category, i_class ) +ORDER BY lochierarchy DESC, + CASE + WHEN lochierarchy = 0 THEN i_category + END, + rank_within_parent +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query87.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query87.sql new file mode 100644 index 000000000..6f58f2e09 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query87.sql @@ -0,0 +1,21 @@ +-- start query 87 in stream 0 using template query87.tpl +select count(*) +from ((select distinct c_last_name, c_first_name, d_date + from store_sales, date_dim, customer + where store_sales.ss_sold_date_sk = date_dim.d_date_sk + and store_sales.ss_customer_sk = customer.c_customer_sk + and d_month_seq between 1188 and 1188+11) + except + (select distinct c_last_name, c_first_name, d_date + from catalog_sales, date_dim, customer + where catalog_sales.cs_sold_date_sk = date_dim.d_date_sk + and catalog_sales.cs_bill_customer_sk = customer.c_customer_sk + and d_month_seq between 1188 and 1188+11) + except + (select distinct c_last_name, c_first_name, d_date + from web_sales, date_dim, customer + where web_sales.ws_sold_date_sk = date_dim.d_date_sk + and web_sales.ws_bill_customer_sk = customer.c_customer_sk + and d_month_seq between 1188 and 1188+11) +) cool_cust +; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query88.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query88.sql new file mode 100644 index 000000000..d1945f341 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query88.sql @@ -0,0 +1,92 @@ +-- start query 88 in stream 0 using template query88.tpl +select * +from + (select count(*) h8_30_to_9 + from store_sales, household_demographics , time_dim, store + where ss_sold_time_sk = time_dim.t_time_sk + and ss_hdemo_sk = household_demographics.hd_demo_sk + and ss_store_sk = s_store_sk + and time_dim.t_hour = 8 + and time_dim.t_minute >= 30 + and ((household_demographics.hd_dep_count = -1 and household_demographics.hd_vehicle_count<=-1+2) or + (household_demographics.hd_dep_count = 2 and household_demographics.hd_vehicle_count<=2+2) or + (household_demographics.hd_dep_count = 3 and household_demographics.hd_vehicle_count<=3+2)) + and store.s_store_name = 'ese') s1, + (select count(*) h9_to_9_30 + from store_sales, household_demographics , time_dim, store + where ss_sold_time_sk = time_dim.t_time_sk + and ss_hdemo_sk = household_demographics.hd_demo_sk + and ss_store_sk = s_store_sk + and time_dim.t_hour = 9 + and time_dim.t_minute < 30 + and ((household_demographics.hd_dep_count = -1 and household_demographics.hd_vehicle_count<=-1+2) or + (household_demographics.hd_dep_count = 2 and household_demographics.hd_vehicle_count<=2+2) or + (household_demographics.hd_dep_count = 3 and household_demographics.hd_vehicle_count<=3+2)) + and store.s_store_name = 'ese') s2, + (select count(*) h9_30_to_10 + from store_sales, household_demographics , time_dim, store + where ss_sold_time_sk = time_dim.t_time_sk + and ss_hdemo_sk = household_demographics.hd_demo_sk + and ss_store_sk = s_store_sk + and time_dim.t_hour = 9 + and time_dim.t_minute >= 30 + and ((household_demographics.hd_dep_count = -1 and household_demographics.hd_vehicle_count<=-1+2) or + (household_demographics.hd_dep_count = 2 and household_demographics.hd_vehicle_count<=2+2) or + (household_demographics.hd_dep_count = 3 and household_demographics.hd_vehicle_count<=3+2)) + and store.s_store_name = 'ese') s3, + (select count(*) h10_to_10_30 + from store_sales, household_demographics , time_dim, store + where ss_sold_time_sk = time_dim.t_time_sk + and ss_hdemo_sk = household_demographics.hd_demo_sk + and ss_store_sk = s_store_sk + and time_dim.t_hour = 10 + and time_dim.t_minute < 30 + and ((household_demographics.hd_dep_count = -1 and household_demographics.hd_vehicle_count<=-1+2) or + (household_demographics.hd_dep_count = 2 and household_demographics.hd_vehicle_count<=2+2) or + (household_demographics.hd_dep_count = 3 and household_demographics.hd_vehicle_count<=3+2)) + and store.s_store_name = 'ese') s4, + (select count(*) h10_30_to_11 + from store_sales, household_demographics , time_dim, store + where ss_sold_time_sk = time_dim.t_time_sk + and ss_hdemo_sk = household_demographics.hd_demo_sk + and ss_store_sk = s_store_sk + and time_dim.t_hour = 10 + and time_dim.t_minute >= 30 + and ((household_demographics.hd_dep_count = -1 and household_demographics.hd_vehicle_count<=-1+2) or + (household_demographics.hd_dep_count = 2 and household_demographics.hd_vehicle_count<=2+2) or + (household_demographics.hd_dep_count = 3 and household_demographics.hd_vehicle_count<=3+2)) + and store.s_store_name = 'ese') s5, + (select count(*) h11_to_11_30 + from store_sales, household_demographics , time_dim, store + where ss_sold_time_sk = time_dim.t_time_sk + and ss_hdemo_sk = household_demographics.hd_demo_sk + and ss_store_sk = s_store_sk + and time_dim.t_hour = 11 + and time_dim.t_minute < 30 + and ((household_demographics.hd_dep_count = -1 and household_demographics.hd_vehicle_count<=-1+2) or + (household_demographics.hd_dep_count = 2 and household_demographics.hd_vehicle_count<=2+2) or + (household_demographics.hd_dep_count = 3 and household_demographics.hd_vehicle_count<=3+2)) + and store.s_store_name = 'ese') s6, + (select count(*) h11_30_to_12 + from store_sales, household_demographics , time_dim, store + where ss_sold_time_sk = time_dim.t_time_sk + and ss_hdemo_sk = household_demographics.hd_demo_sk + and ss_store_sk = s_store_sk + and time_dim.t_hour = 11 + and time_dim.t_minute >= 30 + and ((household_demographics.hd_dep_count = -1 and household_demographics.hd_vehicle_count<=-1+2) or + (household_demographics.hd_dep_count = 2 and household_demographics.hd_vehicle_count<=2+2) or + (household_demographics.hd_dep_count = 3 and household_demographics.hd_vehicle_count<=3+2)) + and store.s_store_name = 'ese') s7, + (select count(*) h12_to_12_30 + from store_sales, household_demographics , time_dim, store + where ss_sold_time_sk = time_dim.t_time_sk + and ss_hdemo_sk = household_demographics.hd_demo_sk + and ss_store_sk = s_store_sk + and time_dim.t_hour = 12 + and time_dim.t_minute < 30 + and ((household_demographics.hd_dep_count = -1 and household_demographics.hd_vehicle_count<=-1+2) or + (household_demographics.hd_dep_count = 2 and household_demographics.hd_vehicle_count<=2+2) or + (household_demographics.hd_dep_count = 3 and household_demographics.hd_vehicle_count<=3+2)) + and store.s_store_name = 'ese') s8 +; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query89.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query89.sql new file mode 100644 index 000000000..1459f9cf9 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query89.sql @@ -0,0 +1,40 @@ +-- start query 89 in stream 0 using template query89.tpl +SELECT * +FROM (SELECT i_category, + i_class, + i_brand, + s_store_name, + s_company_name, + d_moy, + Sum(ss_sales_price) sum_sales, + Avg(Sum(ss_sales_price)) + OVER ( + partition BY i_category, i_brand, s_store_name, s_company_name + ) + avg_monthly_sales + FROM item, + store_sales, + date_dim, + store + WHERE ss_item_sk = i_item_sk + AND ss_sold_date_sk = d_date_sk + AND ss_store_sk = s_store_sk + AND d_year IN ( 2002 ) + AND ( ( i_category IN ( 'Home', 'Men', 'Sports' ) + AND i_class IN ( 'paint', 'accessories', 'fitness' ) ) + OR ( i_category IN ( 'Shoes', 'Jewelry', 'Women' ) + AND i_class IN ( 'mens', 'pendants', 'swimwear' ) ) ) + GROUP BY i_category, + i_class, + i_brand, + s_store_name, + s_company_name, + d_moy) tmp1 +WHERE CASE + WHEN ( avg_monthly_sales <> 0 ) THEN ( + Abs(sum_sales - avg_monthly_sales) / avg_monthly_sales ) + ELSE NULL + END > 0.1 +ORDER BY sum_sales - avg_monthly_sales, + s_store_name +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query9.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query9.sql new file mode 100644 index 000000000..8073df2f9 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query9.sql @@ -0,0 +1,63 @@ +-- start query 9 in stream 0 using template query9.tpl +SELECT CASE + WHEN (SELECT Count(*) + FROM store_sales + WHERE ss_quantity BETWEEN 1 AND 20) > 3672 THEN + (SELECT Avg(ss_ext_list_price) + FROM store_sales + WHERE + ss_quantity BETWEEN 1 AND 20) + ELSE (SELECT Avg(ss_net_profit) + FROM store_sales + WHERE ss_quantity BETWEEN 1 AND 20) + END bucket1, + CASE + WHEN (SELECT Count(*) + FROM store_sales + WHERE ss_quantity BETWEEN 21 AND 40) > 3392 THEN + (SELECT Avg(ss_ext_list_price) + FROM store_sales + WHERE + ss_quantity BETWEEN 21 AND 40) + ELSE (SELECT Avg(ss_net_profit) + FROM store_sales + WHERE ss_quantity BETWEEN 21 AND 40) + END bucket2, + CASE + WHEN (SELECT Count(*) + FROM store_sales + WHERE ss_quantity BETWEEN 41 AND 60) > 32784 THEN + (SELECT Avg(ss_ext_list_price) + FROM store_sales + WHERE + ss_quantity BETWEEN 41 AND 60) + ELSE (SELECT Avg(ss_net_profit) + FROM store_sales + WHERE ss_quantity BETWEEN 41 AND 60) + END bucket3, + CASE + WHEN (SELECT Count(*) + FROM store_sales + WHERE ss_quantity BETWEEN 61 AND 80) > 26032 THEN + (SELECT Avg(ss_ext_list_price) + FROM store_sales + WHERE + ss_quantity BETWEEN 61 AND 80) + ELSE (SELECT Avg(ss_net_profit) + FROM store_sales + WHERE ss_quantity BETWEEN 61 AND 80) + END bucket4, + CASE + WHEN (SELECT Count(*) + FROM store_sales + WHERE ss_quantity BETWEEN 81 AND 100) > 23982 THEN + (SELECT Avg(ss_ext_list_price) + FROM store_sales + WHERE + ss_quantity BETWEEN 81 AND 100) + ELSE (SELECT Avg(ss_net_profit) + FROM store_sales + WHERE ss_quantity BETWEEN 81 AND 100) + END bucket5 +FROM reason +WHERE r_reason_sk = 1; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query90.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query90.sql new file mode 100644 index 000000000..bc117f29e --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query90.sql @@ -0,0 +1,28 @@ + +-- start query 90 in stream 0 using template query90.tpl +SELECT Cast(amc AS DECIMAL(15, 4)) / Cast(pmc AS DECIMAL(15, 4)) + am_pm_ratio +FROM (SELECT Count(*) amc + FROM web_sales, + household_demographics, + time_dim, + web_page + WHERE ws_sold_time_sk = time_dim.t_time_sk + AND ws_ship_hdemo_sk = household_demographics.hd_demo_sk + AND ws_web_page_sk = web_page.wp_web_page_sk + AND time_dim.t_hour BETWEEN 12 AND 12 + 1 + AND household_demographics.hd_dep_count = 8 + AND web_page.wp_char_count BETWEEN 5000 AND 5200) at1, + (SELECT Count(*) pmc + FROM web_sales, + household_demographics, + time_dim, + web_page + WHERE ws_sold_time_sk = time_dim.t_time_sk + AND ws_ship_hdemo_sk = household_demographics.hd_demo_sk + AND ws_web_page_sk = web_page.wp_web_page_sk + AND time_dim.t_hour BETWEEN 20 AND 20 + 1 + AND household_demographics.hd_dep_count = 8 + AND web_page.wp_char_count BETWEEN 5000 AND 5200) pt +ORDER BY am_pm_ratio +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query91.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query91.sql new file mode 100644 index 000000000..457aa8b45 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query91.sql @@ -0,0 +1,32 @@ +-- start query 91 in stream 0 using template query91.tpl +SELECT cc_call_center_id Call_Center, + cc_name Call_Center_Name, + cc_manager Manager, + Sum(cr_net_loss) Returns_Loss +FROM call_center, + catalog_returns, + date_dim, + customer, + customer_address, + customer_demographics, + household_demographics +WHERE cr_call_center_sk = cc_call_center_sk + AND cr_returned_date_sk = d_date_sk + AND cr_returning_customer_sk = c_customer_sk + AND cd_demo_sk = c_current_cdemo_sk + AND hd_demo_sk = c_current_hdemo_sk + AND ca_address_sk = c_current_addr_sk + AND d_year = 1999 + AND d_moy = 12 + AND ( ( cd_marital_status = 'M' + AND cd_education_status = 'Unknown' ) + OR ( cd_marital_status = 'W' + AND cd_education_status = 'Advanced Degree' ) ) + AND hd_buy_potential LIKE 'Unknown%' + AND ca_gmt_offset = -7 +GROUP BY cc_call_center_id, + cc_name, + cc_manager, + cd_marital_status, + cd_education_status +ORDER BY Sum(cr_net_loss) DESC; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query92.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query92.sql new file mode 100644 index 000000000..c1c3dceba --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query92.sql @@ -0,0 +1,22 @@ +-- start query 92 in stream 0 using template query92.tpl +SELECT + Sum(ws_ext_discount_amt) AS `Excess Discount Amount` +FROM web_sales , + item , + date_dim +WHERE i_manufact_id = 718 +AND i_item_sk = ws_item_sk +AND d_date BETWEEN '2002-03-29' AND ( + Cast('2002-03-29' AS DATE) + INTERVAL '90' day) +AND d_date_sk = ws_sold_date_sk +AND ws_ext_discount_amt > + ( + SELECT 1.3 * avg(ws_ext_discount_amt) + FROM web_sales , + date_dim + WHERE ws_item_sk = i_item_sk + AND d_date BETWEEN '2002-03-29' AND ( + cast('2002-03-29' AS date) + INTERVAL '90' day) + AND d_date_sk = ws_sold_date_sk ) +ORDER BY sum(ws_ext_discount_amt) +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query93.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query93.sql new file mode 100644 index 000000000..f7fdc3296 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query93.sql @@ -0,0 +1,22 @@ +-- start query 93 in stream 0 using template query93.tpl +SELECT ss_customer_sk, + Sum(act_sales) sumsales +FROM (SELECT ss_item_sk, + ss_ticket_number, + ss_customer_sk, + CASE + WHEN sr_return_quantity IS NOT NULL THEN + ( ss_quantity - sr_return_quantity ) * ss_sales_price + ELSE ( ss_quantity * ss_sales_price ) + END act_sales + FROM store_sales + LEFT OUTER JOIN store_returns + ON ( sr_item_sk = ss_item_sk + AND sr_ticket_number = ss_ticket_number ), + reason + WHERE sr_reason_sk = r_reason_sk + AND r_reason_desc = 'reason 38') t +GROUP BY ss_customer_sk +ORDER BY sumsales, + ss_customer_sk +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query94.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query94.sql new file mode 100644 index 000000000..e5e8b7568 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query94.sql @@ -0,0 +1,29 @@ +-- start query 94 in stream 0 using template query94.tpl +SELECT + Count(DISTINCT ws_order_number) AS `order count` , + Sum(ws_ext_ship_cost) AS `total shipping cost` , + Sum(ws_net_profit) AS `total net profit` +FROM web_sales ws1 , + date_dim , + customer_address , + web_site +WHERE d_date BETWEEN '2000-3-01' AND ( + Cast('2000-3-01' AS DATE) + INTERVAL '60' day) +AND ws1.ws_ship_date_sk = d_date_sk +AND ws1.ws_ship_addr_sk = ca_address_sk +AND ca_state = 'MT' +AND ws1.ws_web_site_sk = web_site_sk +AND web_company_name = 'pri' +AND EXISTS + ( + SELECT * + FROM web_sales ws2 + WHERE ws1.ws_order_number = ws2.ws_order_number + AND ws1.ws_warehouse_sk <> ws2.ws_warehouse_sk) +AND NOT EXISTS + ( + SELECT * + FROM web_returns wr1 + WHERE ws1.ws_order_number = wr1.wr_order_number) +ORDER BY count(DISTINCT ws_order_number) +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query95.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query95.sql new file mode 100644 index 000000000..d0aa46a95 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query95.sql @@ -0,0 +1,37 @@ +-- start query 95 in stream 0 using template query95.tpl +WITH ws_wh AS +( + SELECT ws1.ws_order_number, + ws1.ws_warehouse_sk wh1, + ws2.ws_warehouse_sk wh2 + FROM web_sales ws1, + web_sales ws2 + WHERE ws1.ws_order_number = ws2.ws_order_number + AND ws1.ws_warehouse_sk <> ws2.ws_warehouse_sk) +SELECT + Count(DISTINCT ws_order_number) AS `order count` , + Sum(ws_ext_ship_cost) AS `total shipping cost` , + Sum(ws_net_profit) AS `total net profit` +FROM web_sales ws1 , + date_dim , + customer_address , + web_site +WHERE d_date BETWEEN '2000-4-01' AND ( + Cast('2000-4-01' AS DATE) + INTERVAL '60' day) +AND ws1.ws_ship_date_sk = d_date_sk +AND ws1.ws_ship_addr_sk = ca_address_sk +AND ca_state = 'IN' +AND ws1.ws_web_site_sk = web_site_sk +AND web_company_name = 'pri' +AND ws1.ws_order_number IN + ( + SELECT ws_order_number + FROM ws_wh) +AND ws1.ws_order_number IN + ( + SELECT wr_order_number + FROM web_returns, + ws_wh + WHERE wr_order_number = ws_wh.ws_order_number) +ORDER BY count(DISTINCT ws_order_number) +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query96.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query96.sql new file mode 100644 index 000000000..1f7731524 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query96.sql @@ -0,0 +1,15 @@ +-- start query 96 in stream 0 using template query96.tpl +SELECT Count(*) +FROM store_sales, + household_demographics, + time_dim, + store +WHERE ss_sold_time_sk = time_dim.t_time_sk + AND ss_hdemo_sk = household_demographics.hd_demo_sk + AND ss_store_sk = s_store_sk + AND time_dim.t_hour = 15 + AND time_dim.t_minute >= 30 + AND household_demographics.hd_dep_count = 7 + AND store.s_store_name = 'ese' +ORDER BY Count(*) +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query97.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query97.sql new file mode 100644 index 000000000..6a6be875a --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query97.sql @@ -0,0 +1,40 @@ + +-- start query 97 in stream 0 using template query97.tpl +WITH ssci + AS (SELECT ss_customer_sk customer_sk, + ss_item_sk item_sk + FROM store_sales, + date_dim + WHERE ss_sold_date_sk = d_date_sk + AND d_month_seq BETWEEN 1196 AND 1196 + 11 + GROUP BY ss_customer_sk, + ss_item_sk), + csci + AS (SELECT cs_bill_customer_sk customer_sk, + cs_item_sk item_sk + FROM catalog_sales, + date_dim + WHERE cs_sold_date_sk = d_date_sk + AND d_month_seq BETWEEN 1196 AND 1196 + 11 + GROUP BY cs_bill_customer_sk, + cs_item_sk) +SELECT Sum(CASE + WHEN ssci.customer_sk IS NOT NULL + AND csci.customer_sk IS NULL THEN 1 + ELSE 0 + END) store_only, + Sum(CASE + WHEN ssci.customer_sk IS NULL + AND csci.customer_sk IS NOT NULL THEN 1 + ELSE 0 + END) catalog_only, + Sum(CASE + WHEN ssci.customer_sk IS NOT NULL + AND csci.customer_sk IS NOT NULL THEN 1 + ELSE 0 + END) store_and_catalog +FROM ssci + FULL OUTER JOIN csci + ON ( ssci.customer_sk = csci.customer_sk + AND ssci.item_sk = csci.item_sk ) +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query98.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query98.sql new file mode 100644 index 000000000..62eaaa518 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query98.sql @@ -0,0 +1,29 @@ + +-- start query 98 in stream 0 using template query98.tpl +SELECT i_item_id, + i_item_desc, + i_category, + i_class, + i_current_price, + Sum(ss_ext_sales_price) AS itemrevenue, + Sum(ss_ext_sales_price) * 100 / Sum(Sum(ss_ext_sales_price)) + OVER ( + PARTITION BY i_class) AS revenueratio +FROM store_sales, + item, + date_dim +WHERE ss_item_sk = i_item_sk + AND i_category IN ( 'Men', 'Home', 'Electronics' ) + AND ss_sold_date_sk = d_date_sk + AND d_date BETWEEN CAST('2000-05-18' AS DATE) AND ( + CAST('2000-05-18' AS DATE) + INTERVAL '30' DAY ) +GROUP BY i_item_id, + i_item_desc, + i_category, + i_class, + i_current_price +ORDER BY i_category, + i_class, + i_item_id, + i_item_desc, + revenueratio; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query99.sql b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query99.sql new file mode 100644 index 000000000..66b433064 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/sparksql/query99.sql @@ -0,0 +1,47 @@ + + +-- start query 99 in stream 0 using template query99.tpl +SELECT Substr(w_warehouse_name, 1, 20), + sm_type, + cc_name, + Sum(CASE + WHEN ( cs_ship_date_sk - cs_sold_date_sk <= 30 ) THEN 1 + ELSE 0 + END) AS `30 days`, + Sum(CASE + WHEN ( cs_ship_date_sk - cs_sold_date_sk > 30 ) + AND ( cs_ship_date_sk - cs_sold_date_sk <= 60 ) THEN 1 + ELSE 0 + END) AS `31-60 days`, + Sum(CASE + WHEN ( cs_ship_date_sk - cs_sold_date_sk > 60 ) + AND ( cs_ship_date_sk - cs_sold_date_sk <= 90 ) THEN 1 + ELSE 0 + END) AS `61-90 days`, + Sum(CASE + WHEN ( cs_ship_date_sk - cs_sold_date_sk > 90 ) + AND ( cs_ship_date_sk - cs_sold_date_sk <= 120 ) THEN + 1 + ELSE 0 + END) AS `91-120 days`, + Sum(CASE + WHEN ( cs_ship_date_sk - cs_sold_date_sk > 120 ) THEN 1 + ELSE 0 + END) AS `>120 days` +FROM catalog_sales, + warehouse, + ship_mode, + call_center, + date_dim +WHERE d_month_seq BETWEEN 1200 AND 1200 + 11 + AND cs_ship_date_sk = d_date_sk + AND cs_warehouse_sk = w_warehouse_sk + AND cs_ship_mode_sk = sm_ship_mode_sk + AND cs_call_center_sk = cc_call_center_sk +GROUP BY Substr(w_warehouse_name, 1, 20), + sm_type, + cc_name +ORDER BY Substr(w_warehouse_name, 1, 20), + sm_type, + cc_name +LIMIT 100; diff --git a/datajunction-server/tests/sql/parsing/queries/tpcds/test_tpcds.py b/datajunction-server/tests/sql/parsing/queries/tpcds/test_tpcds.py new file mode 100644 index 000000000..3bf7e5f04 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/queries/tpcds/test_tpcds.py @@ -0,0 +1,347 @@ +""" +test parsing tpcds queries into DJ ASTs +""" + +# mypy: ignore-errors +# pylint: skip-file +from difflib import SequenceMatcher + +import pytest +import sqlparse + +from datajunction_server.sql.parsing.backends.antlr4 import parse, parse_statement + +ansi_tpcds_files = [ + ("./ansi/query1.sql"), + ("./ansi/query2.sql"), + ("./ansi/query3.sql"), + ("./ansi/query4.sql"), + ("./ansi/query5.sql"), + ("./ansi/query6.sql"), + ("./ansi/query7.sql"), + ("./ansi/query8.sql"), + ("./ansi/query9.sql"), + ("./ansi/query10.sql"), + ("./ansi/query11.sql"), + ("./ansi/query12.sql"), + ("./ansi/query13.sql"), + ("./ansi/query14.sql"), + ("./ansi/query15.sql"), + ("./ansi/query16.sql"), + ("./ansi/query17.sql"), + ("./ansi/query18.sql"), + ("./ansi/query19.sql"), + ("./ansi/query20.sql"), + ("./ansi/query21.sql"), + ("./ansi/query22.sql"), + ("./ansi/query23.sql"), + ("./ansi/query24.sql"), + ("./ansi/query25.sql"), + ("./ansi/query26.sql"), + ("./ansi/query27.sql"), + ("./ansi/query28.sql"), + ("./ansi/query29.sql"), + ("./ansi/query30.sql"), + ("./ansi/query31.sql"), + ("./ansi/query32.sql"), + ("./ansi/query33.sql"), + ("./ansi/query34.sql"), + ("./ansi/query35.sql"), + ("./ansi/query36.sql"), + ("./ansi/query37.sql"), + ("./ansi/query38.sql"), + ("./ansi/query39.sql"), + ("./ansi/query40.sql"), + ("./ansi/query41.sql"), + ("./ansi/query42.sql"), + ("./ansi/query43.sql"), + ("./ansi/query44.sql"), + ("./ansi/query45.sql"), + ("./ansi/query46.sql"), + ("./ansi/query47.sql"), + ("./ansi/query48.sql"), + ("./ansi/query49.sql"), + ("./ansi/query50.sql"), + ("./ansi/query51.sql"), + ("./ansi/query52.sql"), + ("./ansi/query53.sql"), + ("./ansi/query54.sql"), + ("./ansi/query55.sql"), + ("./ansi/query56.sql"), + ("./ansi/query57.sql"), + ("./ansi/query58.sql"), + ("./ansi/query59.sql"), + ("./ansi/query60.sql"), + ("./ansi/query61.sql"), + ("./ansi/query62.sql"), + ("./ansi/query63.sql"), + ("./ansi/query64.sql"), + ("./ansi/query65.sql"), + ("./ansi/query66.sql"), + ("./ansi/query67.sql"), + ("./ansi/query68.sql"), + ("./ansi/query69.sql"), + ("./ansi/query70.sql"), + ("./ansi/query71.sql"), + ("./ansi/query72.sql"), + ("./ansi/query73.sql"), + ("./ansi/query74.sql"), + ("./ansi/query75.sql"), + ("./ansi/query76.sql"), + ("./ansi/query77.sql"), + ("./ansi/query78.sql"), + ("./ansi/query79.sql"), + ("./ansi/query80.sql"), + ("./ansi/query81.sql"), + ("./ansi/query82.sql"), + ("./ansi/query83.sql"), + ("./ansi/query84.sql"), + ("./ansi/query85.sql"), + ("./ansi/query86.sql"), + ("./ansi/query87.sql"), + ("./ansi/query88.sql"), + ("./ansi/query89.sql"), + ("./ansi/query90.sql"), + ("./ansi/query91.sql"), + ("./ansi/query92.sql"), + ("./ansi/query93.sql"), + ("./ansi/query94.sql"), + ("./ansi/query95.sql"), + ("./ansi/query96.sql"), + ("./ansi/query97.sql"), + ("./ansi/query98.sql"), + ("./ansi/query99.sql"), +] + +spark_tpcds_files = [ + ("./sparksql/query1.sql"), + ("./sparksql/query2.sql"), + ("./sparksql/query3.sql"), + ("./sparksql/query4.sql"), + ("./sparksql/query5.sql"), + ("./sparksql/query6.sql"), + ("./sparksql/query7.sql"), + ("./sparksql/query8.sql"), + ("./sparksql/query9.sql"), + ("./sparksql/query10.sql"), + ("./sparksql/query11.sql"), + ("./sparksql/query12.sql"), + ("./sparksql/query13.sql"), + ("./sparksql/query14.sql"), + ("./sparksql/query15.sql"), + ("./sparksql/query16.sql"), + ("./sparksql/query17.sql"), + ("./sparksql/query18.sql"), + ("./sparksql/query19.sql"), + ("./sparksql/query20.sql"), + ("./sparksql/query21.sql"), + ("./sparksql/query22.sql"), + ("./sparksql/query23.sql"), + ("./sparksql/query24.sql"), + ("./sparksql/query25.sql"), + ("./sparksql/query26.sql"), + ("./sparksql/query27.sql"), + ("./sparksql/query28.sql"), + ("./sparksql/query29.sql"), + ("./sparksql/query30.sql"), + ("./sparksql/query31.sql"), + ("./sparksql/query32.sql"), + ("./sparksql/query33.sql"), + ("./sparksql/query34.sql"), + ("./sparksql/query35.sql"), + ("./sparksql/query36.sql"), + ("./sparksql/query37.sql"), + ("./sparksql/query38.sql"), + ("./sparksql/query39.sql"), + ("./sparksql/query40.sql"), + ("./sparksql/query41.sql"), + ("./sparksql/query42.sql"), + ("./sparksql/query43.sql"), + ("./sparksql/query44.sql"), + ("./sparksql/query45.sql"), + ("./sparksql/query46.sql"), + ("./sparksql/query47.sql"), + ("./sparksql/query48.sql"), + ("./sparksql/query49.sql"), + ("./sparksql/query50.sql"), + ("./sparksql/query51.sql"), + ("./sparksql/query52.sql"), + ("./sparksql/query53.sql"), + ("./sparksql/query54.sql"), + ("./sparksql/query55.sql"), + ("./sparksql/query56.sql"), + ("./sparksql/query57.sql"), + ("./sparksql/query58.sql"), + ("./sparksql/query59.sql"), + ("./sparksql/query60.sql"), + ("./sparksql/query61.sql"), + ("./sparksql/query62.sql"), + ("./sparksql/query63.sql"), + ("./sparksql/query64.sql"), + ("./sparksql/query65.sql"), + ("./sparksql/query66.sql"), + ("./sparksql/query67.sql"), + ("./sparksql/query68.sql"), + ("./sparksql/query69.sql"), + ("./sparksql/query70.sql"), + ("./sparksql/query71.sql"), + ("./sparksql/query72.sql"), + ("./sparksql/query73.sql"), + ("./sparksql/query74.sql"), + ("./sparksql/query75.sql"), + ("./sparksql/query76.sql"), + ("./sparksql/query77.sql"), + ("./sparksql/query78.sql"), + ("./sparksql/query79.sql"), + ("./sparksql/query80.sql"), + ("./sparksql/query81.sql"), + ("./sparksql/query82.sql"), + ("./sparksql/query83.sql"), + ("./sparksql/query84.sql"), + ("./sparksql/query85.sql"), + ("./sparksql/query86.sql"), + ("./sparksql/query87.sql"), + ("./sparksql/query88.sql"), + ("./sparksql/query89.sql"), + ("./sparksql/query90.sql"), + ("./sparksql/query91.sql"), + ("./sparksql/query92.sql"), + ("./sparksql/query93.sql"), + ("./sparksql/query94.sql"), + ("./sparksql/query95.sql"), + ("./sparksql/query96.sql"), + ("./sparksql/query97.sql"), + ("./sparksql/query98.sql"), + ("./sparksql/query99.sql"), +] + + +def similar(a, b): + return SequenceMatcher(None, a, b).ratio() + + +@pytest.mark.skipif("not config.getoption('tpcds')") +@pytest.mark.parametrize( + "query_file", + ansi_tpcds_files + spark_tpcds_files, +) +def test_tpcds_parse(query_file, request, monkeypatch): + """ + Test that TPCDS queries parse with no errors + """ + monkeypatch.chdir(request.fspath.dirname) + with open(query_file, encoding="UTF-8") as file: + content = file.read() + for query in content.split(";"): + if not query.isspace(): + parse_statement(query) + + +@pytest.mark.skipif("not config.getoption('tpcds')") +@pytest.mark.parametrize( + "query_file", + ansi_tpcds_files + spark_tpcds_files, +) +def test_tpcds_to_ast(query_file, request, monkeypatch): + """ + Test that TPCDS queries are converted into DJ ASTs with no errors + """ + monkeypatch.chdir(request.fspath.dirname) + with open(query_file, encoding="UTF-8") as file: + content = file.read() + for query in content.split(";"): + if not query.isspace(): + parse(query) + + +@pytest.mark.skipif("not config.getoption('tpcds')") +@pytest.mark.parametrize( + "query_file", + ansi_tpcds_files + spark_tpcds_files, +) +def test_tpcds_circular_parse(query_file, request, monkeypatch): + """ + Test that the string representation of TPCDS DJ ASTs can be re-parsed + """ + monkeypatch.chdir(request.fspath.dirname) + with open(query_file, encoding="UTF-8") as file: + content = file.read() + for query in content.split(";"): + if not query.isspace(): + query_ast = parse(query) + # Below print statements show up when you include --capture=tee-sys + # These are helpful when you want to visually compare the query outputs + print( + """ + """, + ) + print( + f""" + ### ORIGINAL QUERY {query_file} ### + """, + ) + print(sqlparse.format(query, reindent=True, keyword_case="upper")) + print( + """ + ### DJ AST __str__ ### + """, + ) + print( + sqlparse.format( + str(query_ast), + reindent=True, + keyword_case="upper", + ), + ) + print( + """ + """, + ) + + +@pytest.mark.skipif("not config.getoption('tpcds')") +@pytest.mark.parametrize( + "query_file", + ansi_tpcds_files + spark_tpcds_files, +) +def test_tpcds_circular_parse_and_compare(query_file, request, monkeypatch): + """ + Compare the string representation of TPCDS DJ ASTs to the original query + """ + monkeypatch.chdir(request.fspath.dirname) + with open(query_file, encoding="UTF-8") as file: + content = file.read() + for query in content.split(";"): + if not query.isspace(): + query_ast = parse(query) + parse(str(query_ast)) + assert sqlparse.format( + query, + reindent=True, + keyword_case="upper", + ) == sqlparse.format( + str(query_ast), + reindent=True, + keyword_case="upper", + ) + + +@pytest.mark.parametrize( + "query_file", + spark_tpcds_files, +) +def test_tpcds_ast_parse_comparisons( + query_file, + request, + monkeypatch, + compare_query_strings_fixture, +): + """ + Test str -> parse(1) -> DJ AST -> str -> parse(2) and comparing (1) and (2) + """ + monkeypatch.chdir(request.fspath.dirname) + with open(query_file, encoding="UTF-8") as file: + content = file.read() + for query in content.split(";"): + if query.strip(): + assert compare_query_strings_fixture(query, str(parse(query))) diff --git a/datajunction-server/tests/sql/parsing/test_ast.py b/datajunction-server/tests/sql/parsing/test_ast.py new file mode 100644 index 000000000..d8fcea843 --- /dev/null +++ b/datajunction-server/tests/sql/parsing/test_ast.py @@ -0,0 +1,975 @@ +""" +testing ast Nodes and their methods +""" + +import json + +from fastapi.testclient import TestClient +from sqlmodel import Session + +from datajunction_server.errors import DJException +from datajunction_server.models import NodeRevision +from datajunction_server.models.node import Node +from datajunction_server.sql.parsing import ast, types +from datajunction_server.sql.parsing.ast import ( + CompileContext, + deserialize_ast, + serialize_ast, +) +from datajunction_server.sql.parsing.backends.antlr4 import parse + + +def test_ast_compile_table( + session, + client_with_examples, # pylint: disable=unused-argument +): + """ + Test compiling the primary table from a query + + Includes client_with_examples fixture so that examples are loaded into session + """ + query = parse("SELECT hard_hat_id, last_name, first_name FROM default.hard_hats") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.select.from_.relations[0].primary.compile(ctx) + assert not exc.errors + + node = query.select.from_.relations[ # pylint: disable=protected-access + 0 + ].primary._dj_node + assert node + assert node.name == "default.hard_hats" + + +def test_ast_compile_table_missing_node(session): + """ + Test compiling a table when the node is missing + """ + query = parse("SELECT a FROM foo") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.select.from_.relations[0].primary.compile(ctx) + assert "No node `foo` exists of kind" in exc.errors[0].message + + query = parse("SELECT a FROM foo, bar, baz") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.select.from_.relations[0].primary.compile(ctx) + assert "No node `foo` exists of kind" in exc.errors[0].message + query.select.from_.relations[1].primary.compile(ctx) + assert "No node `bar` exists of kind" in exc.errors[1].message + query.select.from_.relations[2].primary.compile(ctx) + assert "No node `baz` exists of kind" in exc.errors[2].message + + query = parse("SELECT a FROM foo LEFT JOIN bar") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.select.from_.relations[0].primary.compile(ctx) + assert "No node `foo` exists of kind" in exc.errors[0].message + query.select.from_.relations[0].extensions[0].right.compile(ctx) + assert "No node `bar` exists of kind" in exc.errors[1].message + + query = parse("SELECT a FROM foo LEFT JOIN (SELECT b FROM bar) b") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.select.from_.relations[0].primary.compile(ctx) + assert "No node `foo` exists of kind" in exc.errors[0].message + query.select.from_.relations[0].extensions[0].right.select.from_.relations[ + 0 + ].primary.compile(ctx) + assert "No node `bar` exists of kind" in exc.errors[1].message + + +def test_ast_compile_query( + session, + client_with_examples, # pylint: disable=unused-argument +): + """ + Test compiling an entire query + """ + query = parse("SELECT hard_hat_id, last_name, first_name FROM default.hard_hats") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + + node = query.select.from_.relations[ # pylint: disable=protected-access + 0 + ].primary._dj_node + assert node + assert node.name == "default.hard_hats" + + +def test_ast_compile_query_missing_columns( + session, + client_with_examples, # pylint: disable=unused-argument +): + """ + Test compiling a query with missing columns + """ + query = parse("SELECT hard_hat_id, column_foo, column_bar FROM default.hard_hats") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert ( + "Column `column_foo` does not exist on any valid table." + in exc.errors[0].message + ) + assert ( + "Column `column_bar` does not exist on any valid table." + in exc.errors[1].message + ) + + node = query.select.from_.relations[ # pylint: disable=protected-access + 0 + ].primary._dj_node + assert node + assert node.name == "default.hard_hats" + + +def test_ast_compile_missing_references(session: Session): + """ + Test getting dependencies from a query that has dangling references when set not to raise + """ + query = parse("select a, b, c from does_not_exist") + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + _, danglers = query.extract_dependencies(ctx) + assert "does_not_exist" in danglers + + +def test_ast_compile_raise_on_ambiguous_column( + session: Session, + client_with_examples, # pylint: disable=unused-argument +): + """ + Test raising on ambiguous column + """ + query = parse( + "SELECT country FROM basic.transform.country_agg a " + "LEFT JOIN basic.dimension.countries b on a.country = b.country", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert ( + "Column `country` found in multiple tables. Consider using fully qualified name." + in exc.errors[0].message + ) + + +def test_ast_compile_having( + session: Session, + client_with_examples, # pylint: disable=unused-argument +): + """ + Test using having + """ + query = parse( + "SELECT order_date, status FROM dbt.source.jaffle_shop.orders " + "GROUP BY dbt.dimension.customers.id " + "HAVING dbt.dimension.customers.id=1", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + assert not exc.errors + + node = query.select.from_.relations[0].primary._dj_node # type: ignore # pylint: disable=protected-access + assert node + assert node.name == "dbt.source.jaffle_shop.orders" + + +def test_ast_compile_lateral_view_explode1(session: Session): + """ + Test lateral view explode + """ + + query = parse( + """SELECT a, b, c, c_age.col, d_age.col + FROM (SELECT 1 as a, 2 as b, 3 as c, ARRAY(30,60) as d, ARRAY(40,80) as e) AS foo + LATERAL VIEW EXPLODE(d) c_age + LATERAL VIEW EXPLODE(e) d_age; + """, + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + + assert not exc.errors + + assert query.columns[0].is_compiled() + assert query.columns[1].is_compiled() + assert query.columns[2].is_compiled() + assert query.columns[3].is_compiled() + assert query.columns[4].is_compiled() + assert query.columns[0].name == ast.Name( # type: ignore + name="a", + quote_style="", + namespace=None, + ) + assert query.columns[1].name == ast.Name( # type: ignore + name="b", + quote_style="", + namespace=None, + ) + assert query.columns[2].name == ast.Name( # type: ignore + name="c", + quote_style="", + namespace=None, + ) + assert query.columns[3].name == ast.Name( # type: ignore + name="col", + quote_style="", + namespace=ast.Name(name="c_age", quote_style="", namespace=None), + ) + assert query.columns[4].name == ast.Name( # type: ignore + name="col", + quote_style="", + namespace=ast.Name(name="d_age", quote_style="", namespace=None), + ) + assert isinstance(query.columns[0].type, types.IntegerType) + assert isinstance(query.columns[1].type, types.IntegerType) + assert isinstance(query.columns[2].type, types.IntegerType) + assert isinstance(query.columns[3].type, types.IntegerType) + assert isinstance(query.columns[4].type, types.IntegerType) + assert query.columns[0].table.alias_or_name == ast.Name( # type: ignore + name="foo", + quote_style="", + namespace=None, + ) + assert query.columns[1].table.alias_or_name == ast.Name( # type: ignore + name="foo", + quote_style="", + namespace=None, + ) + assert query.columns[2].table.alias_or_name == ast.Name( # type: ignore + name="foo", + quote_style="", + namespace=None, + ) + assert query.columns[3].table.alias_or_name == ast.Name( # type: ignore + name="c_age", + quote_style="", + namespace=None, + ) + assert query.columns[4].table.alias_or_name == ast.Name( # type: ignore + name="d_age", + quote_style="", + namespace=None, + ) + + +def test_ast_compile_lateral_view_explode2(session: Session): + """ + Test lateral view explode + """ + + query = parse( + """SELECT a, b, c, c_age, d_age + FROM (SELECT 1 as a, 2 as b, 3 as c, ARRAY(30,60) as d, ARRAY(40,80) as e) AS foo + LATERAL VIEW EXPLODE(d) AS c_age + LATERAL VIEW EXPLODE(e) AS d_age;""", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + + assert query.columns[0].is_compiled() + assert query.columns[1].is_compiled() + assert query.columns[2].is_compiled() + assert query.columns[3].is_compiled() + assert query.columns[4].is_compiled() + assert query.columns[0].name == ast.Name( # type: ignore + name="a", + quote_style="", + namespace=None, + ) + assert query.columns[1].name == ast.Name( # type: ignore + name="b", + quote_style="", + namespace=None, + ) + assert query.columns[2].name == ast.Name( # type: ignore + name="c", + quote_style="", + namespace=None, + ) + assert query.columns[3].name == ast.Name( # type: ignore + name="c_age", + quote_style="", + namespace=None, + ) + assert query.columns[4].name == ast.Name( # type: ignore + name="d_age", + quote_style="", + namespace=None, + ) + assert isinstance(query.columns[0].type, types.IntegerType) + assert isinstance(query.columns[1].type, types.IntegerType) + assert isinstance(query.columns[2].type, types.IntegerType) + assert isinstance(query.columns[3].type, types.IntegerType) + assert isinstance(query.columns[4].type, types.IntegerType) + assert query.columns[0].table.alias_or_name == ast.Name( # type: ignore + name="foo", + quote_style="", + namespace=None, + ) + assert query.columns[1].table.alias_or_name == ast.Name( # type: ignore + name="foo", + quote_style="", + namespace=None, + ) + assert query.columns[2].table.alias_or_name == ast.Name( # type: ignore + name="foo", + quote_style="", + namespace=None, + ) + assert query.columns[3].table.alias_or_name == ast.Name( # type: ignore + name="EXPLODE", + quote_style="", + namespace=None, + ) + assert query.columns[4].table.alias_or_name == ast.Name( # type: ignore + name="EXPLODE", + quote_style="", + namespace=None, + ) + + +def test_ast_compile_lateral_view_explode3(session: Session): + """ + Test lateral view explode of array constant + """ + + query = parse( + """SELECT a, b, c, d, e + FROM (SELECT 1 as a, 2 as b, 3 as c) AS foo + LATERAL VIEW EXPLODE(ARRAY(30, 60)) AS d + LATERAL VIEW EXPLODE(ARRAY(40, 80)) AS e;""", + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + + assert query.columns[0].is_compiled() + assert query.columns[1].is_compiled() + assert query.columns[2].is_compiled() + assert query.columns[3].is_compiled() + assert query.columns[4].is_compiled() + assert query.columns[0].name == ast.Name( # type: ignore + name="a", + quote_style="", + namespace=None, + ) + assert query.columns[1].name == ast.Name( # type: ignore + name="b", + quote_style="", + namespace=None, + ) + assert query.columns[2].name == ast.Name( # type: ignore + name="c", + quote_style="", + namespace=None, + ) + assert query.columns[3].name == ast.Name( # type: ignore + name="d", + quote_style="", + namespace=None, + ) + assert query.columns[4].name == ast.Name( # type: ignore + name="e", + quote_style="", + namespace=None, + ) + assert isinstance(query.columns[0].type, types.IntegerType) + assert isinstance(query.columns[1].type, types.IntegerType) + assert isinstance(query.columns[2].type, types.IntegerType) + assert isinstance(query.columns[3].type, types.IntegerType) + assert isinstance(query.columns[4].type, types.IntegerType) + assert query.columns[0].table.alias_or_name == ast.Name( # type: ignore + name="foo", + quote_style="", + namespace=None, + ) + assert query.columns[1].table.alias_or_name == ast.Name( # type: ignore + name="foo", + quote_style="", + namespace=None, + ) + assert query.columns[2].table.alias_or_name == ast.Name( # type: ignore + name="foo", + quote_style="", + namespace=None, + ) + assert query.columns[3].table.alias_or_name == ast.Name( # type: ignore + name="EXPLODE", + quote_style="", + namespace=None, + ) + assert query.columns[4].table.alias_or_name == ast.Name( # type: ignore + name="EXPLODE", + quote_style="", + namespace=None, + ) + + +def test_ast_compile_lateral_view_explode4(session: Session, client: TestClient): + """ + Test lateral view explode of an upstream column + """ + client.post("/namespaces/default/") + response = client.post("/catalogs/", json={"name": "default"}) + assert response.ok + response = client.post( + "/nodes/source/", + json={ + "columns": [ + {"name": "a", "type": "int"}, + ], + "description": "Placeholder source node", + "mode": "published", + "name": "default.a", + "catalog": "default", + "schema_": "a", + "table": "a", + }, + ) + assert response.ok + response = client.post( + "/nodes/transform/", + json={ + "description": "A projection with an array", + "query": "SELECT ARRAY(30, 60) as foo_array FROM default.a", + "mode": "published", + "name": "default.foo_array_example", + }, + ) + assert response.ok + + query = parse( + """ + SELECT foo_array, a, b + FROM default.foo_array_example + LATERAL VIEW EXPLODE(foo_array) AS a + LATERAL VIEW EXPLODE(foo_array) AS b; + """, + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + + assert query.columns[0].is_compiled() + assert query.columns[1].is_compiled() + assert query.columns[2].is_compiled() + assert query.columns[0].name == ast.Name( # type: ignore + name="foo_array", + quote_style="", + namespace=None, + ) + assert query.columns[1].name == ast.Name( # type: ignore + name="a", + quote_style="", + namespace=None, + ) + assert query.columns[2].name == ast.Name( # type: ignore + name="b", + quote_style="", + namespace=None, + ) + assert isinstance(query.columns[0].type, types.ListType) + assert isinstance(query.columns[1].type, types.IntegerType) + assert isinstance(query.columns[2].type, types.IntegerType) + assert query.columns[0].table.alias_or_name == ast.Name( # type: ignore + name="foo_array_example", + quote_style="", + namespace=ast.Name(name="default", quote_style="", namespace=None), + ) + assert query.columns[1].table.alias_or_name == ast.Name( # type: ignore + name="EXPLODE", + quote_style="", + namespace=None, + ) + assert query.columns[2].table.alias_or_name == ast.Name( # type: ignore + name="EXPLODE", + quote_style="", + namespace=None, + ) + + +def test_ast_compile_lateral_view_explode5(session: Session): + """ + Test both a lateral and horizontal explode + """ + + query = parse( + """SELECT a, b, c, d.col, e.col, EXPLODE(ARRAY(30, 60)) + FROM (SELECT 1 as a, 2 as b, 3 as c) AS foo + LATERAL VIEW EXPLODE(ARRAY(30, 60)) d + LATERAL VIEW EXPLODE(ARRAY(40, 80)) e; + """, + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + + assert not exc.errors + + assert query.columns[0].is_compiled() + assert query.columns[1].is_compiled() + assert query.columns[2].is_compiled() + assert query.columns[3].is_compiled() + assert query.columns[4].is_compiled() + assert query.columns[0].name == ast.Name( # type: ignore + name="a", + quote_style="", + namespace=None, + ) + assert query.columns[1].name == ast.Name( # type: ignore + name="b", + quote_style="", + namespace=None, + ) + assert query.columns[2].name == ast.Name( # type: ignore + name="c", + quote_style="", + namespace=None, + ) + assert query.columns[3].name == ast.Name( # type: ignore + name="col", + quote_style="", + namespace=ast.Name(name="d", quote_style="", namespace=None), + ) + assert query.columns[4].name == ast.Name( # type: ignore + name="col", + quote_style="", + namespace=ast.Name(name="e", quote_style="", namespace=None), + ) + assert query.columns[5].name == ast.Name( # type: ignore + name="col", + quote_style="", + namespace=None, + ) + assert isinstance(query.columns[0].type, types.IntegerType) + assert isinstance(query.columns[1].type, types.IntegerType) + assert isinstance(query.columns[2].type, types.IntegerType) + assert isinstance(query.columns[3].type, types.IntegerType) + assert isinstance(query.columns[4].type, types.IntegerType) + assert isinstance(query.columns[5].type, types.IntegerType) + assert query.columns[0].table.alias_or_name == ast.Name( # type: ignore + name="foo", + quote_style="", + namespace=None, + ) + assert query.columns[1].table.alias_or_name == ast.Name( # type: ignore + name="foo", + quote_style="", + namespace=None, + ) + assert query.columns[2].table.alias_or_name == ast.Name( # type: ignore + name="foo", + quote_style="", + namespace=None, + ) + assert query.columns[3].table.alias_or_name == ast.Name( # type: ignore + name="d", + quote_style="", + namespace=None, + ) + assert query.columns[4].table.alias_or_name == ast.Name( # type: ignore + name="e", + quote_style="", + namespace=None, + ) + assert query.columns[5].table is None # type: ignore + + +def test_ast_compile_lateral_view_explode6(session: Session): + """ + Test lateral view explode of a map (table aliased) + """ + + query = parse( + """SELECT a, b, c, c_age.key, c_age.value, d_age.key, d_age.value + FROM ( + SELECT 1 as a, 2 as b, 3 as c, MAP('a',1,'b',2) as d, MAP('c',1,'d',2) as e + ) AS foo + LATERAL VIEW EXPLODE(d) c_age + LATERAL VIEW EXPLODE(e) d_age; + """, + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + + assert not exc.errors + + assert query.columns[0].is_compiled() + assert query.columns[1].is_compiled() + assert query.columns[2].is_compiled() + assert query.columns[3].is_compiled() + assert query.columns[4].is_compiled() + assert query.columns[5].is_compiled() + assert query.columns[6].is_compiled() + assert query.columns[0].name == ast.Name( # type: ignore + name="a", + quote_style="", + namespace=None, + ) + assert query.columns[1].name == ast.Name( # type: ignore + name="b", + quote_style="", + namespace=None, + ) + assert query.columns[2].name == ast.Name( # type: ignore + name="c", + quote_style="", + namespace=None, + ) + assert query.columns[3].name == ast.Name( # type: ignore + name="key", + quote_style="", + namespace=ast.Name("c_age"), + ) + assert query.columns[4].name == ast.Name( # type: ignore + name="value", + quote_style="", + namespace=ast.Name("c_age"), + ) + assert query.columns[5].name == ast.Name( # type: ignore + name="key", + quote_style="", + namespace=ast.Name("d_age"), + ) + assert query.columns[6].name == ast.Name( # type: ignore + name="value", + quote_style="", + namespace=ast.Name("d_age"), + ) + assert isinstance(query.columns[0].type, types.IntegerType) + assert isinstance(query.columns[1].type, types.IntegerType) + assert isinstance(query.columns[2].type, types.IntegerType) + assert isinstance(query.columns[3].type, types.StringType) + assert isinstance(query.columns[4].type, types.IntegerType) + assert isinstance(query.columns[5].type, types.StringType) + assert isinstance(query.columns[6].type, types.IntegerType) + assert query.columns[0].table.alias_or_name == ast.Name( # type: ignore + name="foo", + quote_style="", + namespace=None, + ) + assert query.columns[1].table.alias_or_name == ast.Name( # type: ignore + name="foo", + quote_style="", + namespace=None, + ) + assert query.columns[2].table.alias_or_name == ast.Name( # type: ignore + name="foo", + quote_style="", + namespace=None, + ) + assert query.columns[3].table.alias_or_name == ast.Name( # type: ignore + name="c_age", + quote_style="", + namespace=None, + ) + assert query.columns[4].table.alias_or_name == ast.Name( # type: ignore + name="c_age", + quote_style="", + namespace=None, + ) + assert query.columns[5].table.alias_or_name == ast.Name( # type: ignore + name="d_age", + quote_style="", + namespace=None, + ) + assert query.columns[6].table.alias_or_name == ast.Name( # type: ignore + name="d_age", + quote_style="", + namespace=None, + ) + + +def test_ast_compile_lateral_view_explode7(session: Session): + """ + Test lateral view explode of a map (column aliased) + """ + + query = parse( + """SELECT a, b, c, k1, v1, k2, v2 + FROM ( + SELECT 1 as a, 2 as b, 3 as c, MAP('a',1,'b',2) as d, MAP('c',1,'d',2) as e + ) AS foo + LATERAL VIEW EXPLODE(d) AS k1, v1 + LATERAL VIEW EXPLODE(e) AS k2, v2; + """, + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + + assert not exc.errors + + assert query.columns[0].is_compiled() + assert query.columns[1].is_compiled() + assert query.columns[2].is_compiled() + assert query.columns[3].is_compiled() + assert query.columns[4].is_compiled() + assert query.columns[5].is_compiled() + assert query.columns[6].is_compiled() + assert query.columns[0].name == ast.Name( # type: ignore + name="a", + quote_style="", + namespace=None, + ) + assert query.columns[1].name == ast.Name( # type: ignore + name="b", + quote_style="", + namespace=None, + ) + assert query.columns[2].name == ast.Name( # type: ignore + name="c", + quote_style="", + namespace=None, + ) + assert query.columns[3].name == ast.Name( # type: ignore + name="k1", + quote_style="", + namespace=None, + ) + assert query.columns[4].name == ast.Name( # type: ignore + name="v1", + quote_style="", + namespace=None, + ) + assert query.columns[5].name == ast.Name( # type: ignore + name="k2", + quote_style="", + namespace=None, + ) + assert query.columns[6].name == ast.Name( # type: ignore + name="v2", + quote_style="", + namespace=None, + ) + assert isinstance(query.columns[0].type, types.IntegerType) + assert isinstance(query.columns[1].type, types.IntegerType) + assert isinstance(query.columns[2].type, types.IntegerType) + assert isinstance(query.columns[3].type, types.StringType) + assert isinstance(query.columns[4].type, types.IntegerType) + assert isinstance(query.columns[5].type, types.StringType) + assert isinstance(query.columns[6].type, types.IntegerType) + assert query.columns[0].table.alias_or_name == ast.Name( # type: ignore + name="foo", + quote_style="", + namespace=None, + ) + assert query.columns[1].table.alias_or_name == ast.Name( # type: ignore + name="foo", + quote_style="", + namespace=None, + ) + assert query.columns[2].table.alias_or_name == ast.Name( # type: ignore + name="foo", + quote_style="", + namespace=None, + ) + assert query.columns[3].table.alias_or_name == ast.Name( # type: ignore + name="EXPLODE", + quote_style="", + namespace=None, + ) + assert query.columns[4].table.alias_or_name == ast.Name( # type: ignore + name="EXPLODE", + quote_style="", + namespace=None, + ) + assert query.columns[5].table.alias_or_name == ast.Name( # type: ignore + name="EXPLODE", + quote_style="", + namespace=None, + ) + assert query.columns[6].table.alias_or_name == ast.Name( # type: ignore + name="EXPLODE", + quote_style="", + namespace=None, + ) + + +def test_ast_compile_lateral_view_explode8(session: Session): + """ + Test lateral view explode of a map (both table and column aliased) + """ + + query = parse( + """SELECT a, b, c, c_age.k1, c_age.v1, d_age.k2, d_age.v2 + FROM ( + SELECT 1 as a, 2 as b, 3 as c, MAP('a',1,'b',2) as d, MAP('c',1,'d',2) as e + ) AS foo + LATERAL VIEW EXPLODE(d) c_age AS k1, v1 + LATERAL VIEW EXPLODE(e) d_age AS k2, v2; + """, + ) + exc = DJException() + ctx = ast.CompileContext(session=session, exception=exc) + query.compile(ctx) + + assert not exc.errors + + assert query.columns[0].is_compiled() + assert query.columns[1].is_compiled() + assert query.columns[2].is_compiled() + assert query.columns[3].is_compiled() + assert query.columns[4].is_compiled() + assert query.columns[5].is_compiled() + assert query.columns[6].is_compiled() + assert query.columns[0].name == ast.Name( # type: ignore + name="a", + quote_style="", + namespace=None, + ) + assert query.columns[1].name == ast.Name( # type: ignore + name="b", + quote_style="", + namespace=None, + ) + assert query.columns[2].name == ast.Name( # type: ignore + name="c", + quote_style="", + namespace=None, + ) + assert query.columns[3].name == ast.Name( # type: ignore + name="k1", + quote_style="", + namespace=ast.Name("c_age"), + ) + assert query.columns[4].name == ast.Name( # type: ignore + name="v1", + quote_style="", + namespace=ast.Name("c_age"), + ) + assert query.columns[5].name == ast.Name( # type: ignore + name="k2", + quote_style="", + namespace=ast.Name("d_age"), + ) + assert query.columns[6].name == ast.Name( # type: ignore + name="v2", + quote_style="", + namespace=ast.Name("d_age"), + ) + assert isinstance(query.columns[0].type, types.IntegerType) + assert isinstance(query.columns[1].type, types.IntegerType) + assert isinstance(query.columns[2].type, types.IntegerType) + assert isinstance(query.columns[3].type, types.StringType) + assert isinstance(query.columns[4].type, types.IntegerType) + assert isinstance(query.columns[5].type, types.StringType) + assert isinstance(query.columns[6].type, types.IntegerType) + assert query.columns[0].table.alias_or_name == ast.Name( # type: ignore + name="foo", + quote_style="", + namespace=None, + ) + assert query.columns[1].table.alias_or_name == ast.Name( # type: ignore + name="foo", + quote_style="", + namespace=None, + ) + assert query.columns[2].table.alias_or_name == ast.Name( # type: ignore + name="foo", + quote_style="", + namespace=None, + ) + assert query.columns[3].table.alias_or_name == ast.Name( # type: ignore + name="c_age", + quote_style="", + namespace=None, + ) + assert query.columns[4].table.alias_or_name == ast.Name( # type: ignore + name="c_age", + quote_style="", + namespace=None, + ) + assert query.columns[5].table.alias_or_name == ast.Name( # type: ignore + name="d_age", + quote_style="", + namespace=None, + ) + assert query.columns[6].table.alias_or_name == ast.Name( # type: ignore + name="d_age", + quote_style="", + namespace=None, + ) + + +def test_serde_uncompiled(): + """ + tests that serialization-deserialization preserves the query + """ + tree = parse( + """ + SELECT + customer_id, + COUNT(DISTINCT order_id) AS order_count, + SUM(order_total) AS total_sales + FROM + orders + WHERE + order_date >= '2023-01-01' AND order_date < '2024-01-01' + GROUP BY + customer_id + HAVING + order_count >= 3 + ORDER BY + total_sales DESC + LIMIT 10 + """, + ) + serialized = serialize_ast(tree) + deserialized = deserialize_ast(id(tree), serialized) + assert tree.compare(deserialized) + + +def test_serde_jsonable(): + """ + tests that serialized ast is json serializable + """ + tree = parse( + """ + SELECT + customer_id, + COUNT(DISTINCT order_id) AS order_count, + SUM(order_total) AS total_sales + FROM + orders + WHERE + order_date >= '2023-01-01' AND order_date < '2024-01-01' + GROUP BY + customer_id + HAVING + order_count >= 3 + ORDER BY + total_sales DESC + LIMIT 10 + """, + ) + json.dumps(serialize_ast(tree)) + + +def test_compile_node_serde(construction_session: Session): + """ + Test compiling a node + """ + node_a = Node(name="A", current_version="1") + node_a_rev = NodeRevision( + node=node_a, + version="1", + query="SELECT country FROM basic.transform.country_agg", + ) + tree = parse(node_a_rev.query) + ctx = CompileContext(session=construction_session, exception=DJException()) + tree.compile(ctx) + serialized = serialize_ast(tree) + deserialized = deserialize_ast(id(tree), serialized) + assert tree.compare(deserialized) diff --git a/datajunction-server/tests/sql/utils.py b/datajunction-server/tests/sql/utils.py new file mode 100644 index 000000000..56cc3f5eb --- /dev/null +++ b/datajunction-server/tests/sql/utils.py @@ -0,0 +1,73 @@ +""" +Helper functions. +""" +import os + +from sqlalchemy.sql import Select + +from datajunction_server.sql.parsing import ast +from datajunction_server.sql.parsing.backends.antlr4 import parse + +TPCDS_QUERY_SET = ["tpcds_q01", "tpcds_q99"] + + +def query_to_string(query: Select) -> str: + """ + Helper function to compile a SQLAlchemy query to a string. + """ + return str(query.compile(compile_kwargs={"literal_binds": True})) + + +def compare_query_strings(str1: str, str2: str) -> bool: + """ + compare two query strings + """ + query1 = parse(str1) + query1.select.projection = sorted( + query1.select.projection, + key=lambda x: str(x.alias_or_name), # type: ignore + )[:] + for cte in query1.ctes: + cte.select.projection = sorted( + query1.select.projection, + key=lambda x: str(x.alias_or_name), # type: ignore + )[:] + + query2 = parse(str2) + query2.select.projection = sorted( + query2.select.projection, + key=lambda x: str(x.alias_or_name), # type: ignore + )[:] + for cte in query2.ctes: + cte.select.projection = sorted( + query2.select.projection, + key=lambda x: str(x.alias_or_name), # type: ignore + )[:] + + for relation in query1.find_all(ast.Relation): + relation.extensions = sorted( + relation.extensions, + key=lambda ext: str(ext.right.alias_or_name), # type: ignore + ) + for relation in query2.find_all(ast.Relation): + relation.extensions = sorted( + relation.extensions, + key=lambda ext: str(ext.right.alias_or_name), # type: ignore + ) + return parse(str(query1)).compare(parse(str(query2))) + + +def read_query(name: str) -> str: + """ + Read a tpcds query given filename e.g. tpcds_q01.sql + """ + with open( + os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "parsing", + "queries", + name, + ), + encoding="utf-8", + ) as file: + return file.read() diff --git a/datajunction-server/tests/superset_test.py b/datajunction-server/tests/superset_test.py new file mode 100644 index 000000000..a2d5ada36 --- /dev/null +++ b/datajunction-server/tests/superset_test.py @@ -0,0 +1,72 @@ +""" +Tests for the Superset DB engine spec. +""" + +from pytest_mock import MockerFixture +from requests_mock.mocker import Mocker +from yarl import URL + +from datajunction_server.superset import DJEngineSpec + + +def test_select_star() -> None: + """ + Test ``select_star``. + """ + assert DJEngineSpec.select_star() == ( + "SELECT 'DJ does not support data preview, since the `metrics` table is a " + "virtual table representing the whole repository of metrics. An " + "administrator should configure the DJ database with the " + "`disable_data_preview` attribute set to `true` in the `extra` field.' AS " + "warning" + ) + + +def test_get_metrics(mocker: MockerFixture, requests_mock: Mocker) -> None: + """ + Test ``get_metrics``. + """ + database = mocker.MagicMock() + with database.get_sqla_engine_with_context() as engine: + engine.connect().connection.base_url = URL( + "https://localhost:8000/0", + ) + inspector = mocker.MagicMock() + + requests_mock.get( + "https://localhost:8000/0/metrics", + json=["core.num_comments"], + ) + + assert DJEngineSpec.get_metrics(database, inspector, "some-table", "main") == [ + { + "metric_name": "core.num_comments", + "expression": '"core.num_comments"', + "description": "", + }, + ] + + +def test_get_view_names(mocker: MockerFixture) -> None: + """ + Test ``get_view_names``. + """ + database = mocker.MagicMock() + inspector = mocker.MagicMock() + assert DJEngineSpec.get_view_names(database, inspector, "main") == set() + + +def test_execute(mocker: MockerFixture) -> None: + """ + Test ``execute``. + + The method is almost identical to the superclass, with the only difference that it + quotes identifiers starting with an underscore. + """ + cursor = mocker.MagicMock() + super_ = mocker.patch("datajunction_server.superset.super") + DJEngineSpec.execute(cursor, "SELECT time AS __timestamp FROM table") + super_().execute.assert_called_with( + cursor, + 'SELECT time AS "__timestamp" FROM table', + ) diff --git a/datajunction-server/tests/utils_test.py b/datajunction-server/tests/utils_test.py new file mode 100644 index 000000000..09bf23684 --- /dev/null +++ b/datajunction-server/tests/utils_test.py @@ -0,0 +1,123 @@ +""" +Tests for ``datajunction_server.utils``. +""" + +import logging + +import pytest +from pytest_mock import MockerFixture +from sqlalchemy.engine.url import make_url +from yarl import URL + +from datajunction_server.config import Settings +from datajunction_server.errors import DJException +from datajunction_server.utils import ( + Version, + get_engine, + get_issue_url, + get_query_service_client, + get_session, + get_settings, + setup_logging, +) + + +def test_setup_logging() -> None: + """ + Test ``setup_logging``. + """ + setup_logging("debug") + assert logging.root.level == logging.DEBUG + + with pytest.raises(ValueError) as excinfo: + setup_logging("invalid") + assert str(excinfo.value) == "Invalid log level: invalid" + + +def test_get_session(mocker: MockerFixture) -> None: + """ + Test ``get_session``. + """ + mocker.patch("datajunction_server.utils.get_engine") + Session = mocker.patch( # pylint: disable=invalid-name + "datajunction_server.utils.Session", + ) + + session = next(get_session()) + + assert session == Session().__enter__.return_value + + +def test_get_settings(mocker: MockerFixture) -> None: + """ + Test ``get_settings``. + """ + mocker.patch("datajunction_server.utils.load_dotenv") + Settings = mocker.patch( # pylint: disable=invalid-name, redefined-outer-name + "datajunction_server.utils.Settings", + ) + + # should be already cached, since it's called by the Celery app + get_settings() + Settings.assert_not_called() + + +def test_get_issue_url() -> None: + """ + Test ``get_issue_url``. + """ + assert get_issue_url() == URL( + "https://github.com/DataJunction/dj/issues/new", + ) + assert get_issue_url( + baseurl=URL("https://example.org/"), + title="Title with spaces", + body="This is the body", + labels=["help", "troubleshoot"], + ) == URL( + "https://example.org/?title=Title+with+spaces&" + "body=This+is+the+body&labels=help,troubleshoot", + ) + + +def test_get_engine(mocker: MockerFixture, settings: Settings) -> None: + """ + Test ``get_engine``. + """ + mocker.patch("datajunction_server.utils.get_settings", return_value=settings) + engine = get_engine() + assert engine.url == make_url("sqlite://") + + +def test_get_query_service_client(mocker: MockerFixture, settings: Settings) -> None: + """ + Test ``get_query_service_client``. + """ + settings.query_service = "http://query_service:8001" + mocker.patch("datajunction_server.utils.get_settings", return_value=settings) + query_service_client = get_query_service_client() + assert query_service_client.uri == "http://query_service:8001" # type: ignore + + +def test_version_parse() -> None: + """ + Test version parsing + """ + ver = Version.parse("v1.0") + assert ver.major == 1 + assert ver.minor == 0 + assert str(ver.next_major_version()) == "v2.0" + assert str(ver.next_minor_version()) == "v1.1" + assert str(ver.next_minor_version().next_minor_version()) == "v1.2" + + ver = Version.parse("v21.12") + assert ver.major == 21 + assert ver.minor == 12 + assert str(ver.next_major_version()) == "v22.0" + assert str(ver.next_minor_version()) == "v21.13" + assert str(ver.next_minor_version().next_minor_version()) == "v21.14" + assert str(ver.next_major_version().next_minor_version()) == "v22.1" + + with pytest.raises(DJException) as excinfo: + Version.parse("0") + assert str(excinfo.value) == "Unparseable version 0!" diff --git a/datajunction-ui/.babel-plugin-macrosrc.js b/datajunction-ui/.babel-plugin-macrosrc.js new file mode 100644 index 000000000..7ea38d073 --- /dev/null +++ b/datajunction-ui/.babel-plugin-macrosrc.js @@ -0,0 +1,5 @@ +module.exports = { + styledComponents: { + displayName: process.env.NODE_ENV !== 'production', + }, +}; diff --git a/datajunction-ui/.env b/datajunction-ui/.env new file mode 100644 index 000000000..20245df3a --- /dev/null +++ b/datajunction-ui/.env @@ -0,0 +1,2 @@ +REACT_APP_DJ_URL=http://localhost:8000 +REACT_USE_SSE=true \ No newline at end of file diff --git a/datajunction-ui/.eslintrc.js b/datajunction-ui/.eslintrc.js new file mode 100644 index 000000000..4402f5bab --- /dev/null +++ b/datajunction-ui/.eslintrc.js @@ -0,0 +1,20 @@ +const fs = require('fs'); +const path = require('path'); + +const prettierOptions = JSON.parse( + fs.readFileSync(path.resolve(__dirname, '.prettierrc'), 'utf8'), +); + +module.exports = { + extends: ['react-app', 'prettier'], + plugins: ['prettier'], + rules: { + 'prettier/prettier': ['error', prettierOptions], + }, + overrides: [ + { + files: ['**/*.ts?(x)'], + rules: { 'prettier/prettier': ['warn', prettierOptions] }, + }, + ], +}; diff --git a/datajunction-ui/.gitattributes b/datajunction-ui/.gitattributes new file mode 100644 index 000000000..37bdee27c --- /dev/null +++ b/datajunction-ui/.gitattributes @@ -0,0 +1,201 @@ +# From https://github.com/Danimoth/gitattributes/blob/master/Web.gitattributes + +## GITATTRIBUTES FOR WEB PROJECTS +# +# These settings are for any web project. +# +# Details per file setting: +# text These files should be normalized (i.e. convert CRLF to LF). +# binary These files are binary and should be left untouched. +# +# Note that binary is a macro for -text -diff. +###################################################################### + +# Auto detect +## Handle line endings automatically for files detected as +## text and leave all files detected as binary untouched. +## This will handle all files NOT defined below. +* text=auto + +# Source code +*.bash text eol=lf +*.bat text eol=crlf +*.cmd text eol=crlf +*.coffee text +*.css text +*.htm text diff=html +*.html text diff=html +*.inc text +*.ini text +*.js text +*.json text +*.jsx text +*.less text +*.ls text +*.map text -diff +*.od text +*.onlydata text +*.php text diff=php +*.pl text +*.ps1 text eol=crlf +*.py text diff=python +*.rb text diff=ruby +*.sass text +*.scm text +*.scss text diff=css +*.sh text eol=lf +*.sql text +*.styl text +*.tag text +*.ts text +*.tsx text +*.xml text +*.xhtml text diff=html + +# Docker +Dockerfile text + +# Documentation +*.ipynb text +*.markdown text +*.md text +*.mdwn text +*.mdown text +*.mkd text +*.mkdn text +*.mdtxt text +*.mdtext text +*.txt text +AUTHORS text +CHANGELOG text +CHANGES text +CONTRIBUTING text +COPYING text +copyright text +*COPYRIGHT* text +INSTALL text +license text +LICENSE text +NEWS text +readme text +*README* text +TODO text + +# Templates +*.dot text +*.ejs text +*.haml text +*.handlebars text +*.hbs text +*.hbt text +*.jade text +*.latte text +*.mustache text +*.njk text +*.phtml text +*.tmpl text +*.tpl text +*.twig text +*.vue text + +# Configs +*.cnf text +*.conf text +*.config text +.editorconfig text +.env text +.gitattributes text +.gitconfig text +.htaccess text +*.lock text -diff +package-lock.json text -diff +*.toml text +*.yaml text +*.yml text +browserslist text +Makefile text +makefile text + +# Heroku +Procfile text + +# Graphics +*.ai binary +*.bmp binary +*.eps binary +*.gif binary +*.gifv binary +*.ico binary +*.jng binary +*.jp2 binary +*.jpg binary +*.jpeg binary +*.jpx binary +*.jxr binary +*.pdf binary +*.png binary +*.psb binary +*.psd binary +# SVG treated as an asset (binary) by default. +*.svg text +# If you want to treat it as binary, +# use the following line instead. +# *.svg binary +*.svgz binary +*.tif binary +*.tiff binary +*.wbmp binary +*.webp binary + +# Audio +*.kar binary +*.m4a binary +*.mid binary +*.midi binary +*.mp3 binary +*.ogg binary +*.ra binary + +# Video +*.3gpp binary +*.3gp binary +*.as binary +*.asf binary +*.asx binary +*.fla binary +*.flv binary +*.m4v binary +*.mng binary +*.mov binary +*.mp4 binary +*.mpeg binary +*.mpg binary +*.ogv binary +*.swc binary +*.swf binary +*.webm binary + +# Archives +*.7z binary +*.gz binary +*.jar binary +*.rar binary +*.tar binary +*.zip binary + +# Fonts +*.ttf binary +*.eot binary +*.otf binary +*.woff binary +*.woff2 binary + +# Executables +*.exe binary +*.pyc binary + +# RC files (like .babelrc or .eslintrc) +*.*rc text + +# Ignore files (like .npmignore or .gitignore) +*.*ignore text \ No newline at end of file diff --git a/datajunction-ui/.github/pull_request_template.md b/datajunction-ui/.github/pull_request_template.md new file mode 100644 index 000000000..dbfa966d9 --- /dev/null +++ b/datajunction-ui/.github/pull_request_template.md @@ -0,0 +1,11 @@ +### Summary + + + +### Test Plan + + + +### Deployment Plan + + diff --git a/datajunction-ui/.github/workflows/ci.yml b/datajunction-ui/.github/workflows/ci.yml new file mode 100644 index 000000000..202ad49d2 --- /dev/null +++ b/datajunction-ui/.github/workflows/ci.yml @@ -0,0 +1,33 @@ +name: CI/CD + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [19.x] + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Set up Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + + - name: Install Dependencies + run: yarn install + + - name: Run Unit Tests + run: yarn test + + - name: Build Project + run: yarn build diff --git a/datajunction-ui/.gitignore b/datajunction-ui/.gitignore new file mode 100644 index 000000000..2c287157f --- /dev/null +++ b/datajunction-ui/.gitignore @@ -0,0 +1,35 @@ +# Don't check auto-generated stuff into git +coverage +build +node_modules +dist +stats.json +.pnp +.pnp.js +dist + +# misc +.DS_Store +npm-debug.log* + +# yarn +yarn-debug.log* +yarn-error.log* +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions + +# env +.env.development.local +.env.test.local +.env.production.local + +# boilerplate internals +generated-cra-app +.cra-template-rb +template +.eslintcache diff --git a/datajunction-ui/.husky/pre-commit b/datajunction-ui/.husky/pre-commit new file mode 100755 index 000000000..028978219 --- /dev/null +++ b/datajunction-ui/.husky/pre-commit @@ -0,0 +1,6 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +yarn checkTs +yarn lint-staged + diff --git a/datajunction-ui/.npmrc b/datajunction-ui/.npmrc new file mode 100644 index 000000000..1dab4ed4c --- /dev/null +++ b/datajunction-ui/.npmrc @@ -0,0 +1 @@ +save-exact = true diff --git a/datajunction-ui/.nvmrc b/datajunction-ui/.nvmrc new file mode 100644 index 000000000..518633e16 --- /dev/null +++ b/datajunction-ui/.nvmrc @@ -0,0 +1 @@ +lts/fermium diff --git a/datajunction-ui/.prettierignore b/datajunction-ui/.prettierignore new file mode 100644 index 000000000..e88431261 --- /dev/null +++ b/datajunction-ui/.prettierignore @@ -0,0 +1,6 @@ +build/ +node_modules/ +package-lock.json +yarn.lock +dist/ +coverage/ diff --git a/datajunction-ui/.prettierrc b/datajunction-ui/.prettierrc new file mode 100644 index 000000000..b88daf722 --- /dev/null +++ b/datajunction-ui/.prettierrc @@ -0,0 +1,9 @@ +{ + "printWidth": 80, + "tabWidth": 2, + "useTabs": false, + "semi": true, + "singleQuote": true, + "trailingComma": "all", + "arrowParens": "avoid" +} diff --git a/datajunction-ui/.stylelintrc b/datajunction-ui/.stylelintrc new file mode 100644 index 000000000..9e72e47c6 --- /dev/null +++ b/datajunction-ui/.stylelintrc @@ -0,0 +1,7 @@ +{ + "processors": ["stylelint-processor-styled-components"], + "extends": [ + "stylelint-config-recommended", + "stylelint-config-styled-components" + ] +} diff --git a/datajunction-ui/LICENSE b/datajunction-ui/LICENSE new file mode 100644 index 000000000..874d3043d --- /dev/null +++ b/datajunction-ui/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2023 DJ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/datajunction-ui/README.md b/datajunction-ui/README.md new file mode 100644 index 000000000..c6bbd93a0 --- /dev/null +++ b/datajunction-ui/README.md @@ -0,0 +1,10 @@ +# DataJunction UI + +A view-only UI for the DataJunction metrics platform. + +To run: + +```bash +yarn install +yarn start +``` diff --git a/datajunction-ui/internals/testing/loadable.mock.tsx b/datajunction-ui/internals/testing/loadable.mock.tsx new file mode 100644 index 000000000..ed19a3a59 --- /dev/null +++ b/datajunction-ui/internals/testing/loadable.mock.tsx @@ -0,0 +1,6 @@ +import * as React from 'react'; + +export function ExportedFunc() { + return
My lazy-loaded component
; +} +export default ExportedFunc; diff --git a/datajunction-ui/package.json b/datajunction-ui/package.json new file mode 100644 index 000000000..ac2eaeee2 --- /dev/null +++ b/datajunction-ui/package.json @@ -0,0 +1,165 @@ +{ + "name": "datajunction-ui", + "version": "0.0.1-rc.15", + "description": "DataJunction Metrics Platform UI", + "module": "src/index.tsx", + "repository": { + "type": "git", + "url": "git+https://github.com/DataJunction/dj-ui.git" + }, + "keywords": [ + "datajunction", + "metrics", + "metrics-platform", + "semantic-layer" + ], + "author": "DataJunction Authors", + "license": "MIT", + "bugs": { + "url": "https://github.com/DataJunction/dj/issues" + }, + "homepage": "https://github.com/DataJunction/dj#readme", + "dependencies": { + "@babel/core": "^7.18.2", + "@babel/preset-env": "^7.18.2", + "@babel/preset-react": "7.18.6", + "@reduxjs/toolkit": "1.8.5", + "@testing-library/jest-dom": "5.16.5", + "@testing-library/react": "13.4.0", + "@types/fontfaceobserver": "^2.1.0", + "@types/jest": "^27.5.2", + "@types/node": "^14.18.27", + "@types/react": "^18.0.20", + "@types/react-dom": "^18.0.6", + "@types/react-redux": "^7.1.24", + "@types/react-select": "5.0.1", + "@types/react-test-renderer": "^18.0.0", + "@types/rimraf": "^3.0.2", + "@types/shelljs": "^0.8.11", + "@types/testing-library__jest-dom": "^5.14.5", + "@types/webpack": "^5.28.0", + "@types/webpack-env": "^1.18.0", + "babel-loader": "9.1.2", + "chalk": "4.1.2", + "cronstrue": "2.27.0", + "cross-env": "7.0.3", + "css-loader": "6.7.3", + "dagre": "^0.8.5", + "datajunction": "0.0.1-rc.0", + "file-loader": "6.2.0", + "fontfaceobserver": "2.3.0", + "husky": "8.0.1", + "i18next": "21.9.2", + "i18next-browser-languagedetector": "6.1.5", + "i18next-scanner": "4.0.0", + "inquirer": "7.3.3", + "inquirer-directory": "2.2.0", + "lint-staged": "13.0.3", + "node-plop": "0.26.3", + "plop": "2.7.6", + "prettier": "2.7.1", + "react": "18.2.0", + "react-app-polyfill": "3.0.0", + "react-dom": "18.2.0", + "react-helmet-async": "1.3.0", + "react-i18next": "11.18.6", + "react-is": "18.2.0", + "react-redux": "7.2.8", + "react-router-dom": "6.3.0", + "react-scripts": "5.0.1", + "react-select": "5.7.3", + "react-syntax-highlighter": "^15.5.0", + "react-test-renderer": "18.2.0", + "reactflow": "^11.7.0", + "redux-injectors": "1.3.0", + "redux-saga": "1.2.1", + "rimraf": "3.0.2", + "sanitize.css": "13.0.0", + "serve": "14.0.1", + "shelljs": "0.8.5", + "sql-formatter": "^12.2.0", + "style-loader": "3.3.2", + "stylelint": "14.12.0", + "stylelint-config-recommended": "9.0.0", + "ts-loader": "9.4.2", + "ts-node": "10.9.1", + "typescript": "4.6.4", + "web-vitals": "2.1.4", + "webpack": "5.81.0", + "webpack-cli": "5.0.2", + "webpack-dev-server": "4.13.3" + }, + "scripts": { + "webpack-start": "webpack-dev-server --open", + "webpack-build": "webpack", + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject", + "start:prod": "yarn run build && serve -s build", + "test:generators": "ts-node ./internals/testing/generators/test-generators.ts", + "checkTs": "tsc --noEmit", + "eslint": "eslint --ext js,ts,tsx", + "lint": "yarn run eslint src", + "lint:fix": "yarn run eslint --fix src", + "lint:css": "stylelint src/**/*.css", + "generate": "plop --plopfile internals/generators/plopfile.ts", + "cleanAndSetup": "ts-node ./internals/scripts/clean.ts", + "prettify": "prettier --write", + "extract-messages": "i18next-scanner --config=internals/extractMessages/i18next-scanner.config.js", + "prepublishOnly": "webpack --mode=production" + }, + "eslintConfig": { + "extends": "react-app" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "engines": { + "node": ">=14.x" + }, + "lint-staged": { + "*.{ts,tsx,js,jsx}": [ + "yarn run eslint --fix" + ], + "*.{md,json}": [ + "prettier --write" + ] + }, + "jest": { + "collectCoverageFrom": [ + "src/**/*.{js,jsx,ts,tsx}", + "!src/**/*/*.d.ts", + "!src/**/*/Loadable.{js,jsx,ts,tsx}", + "!src/**/*/messages.ts", + "!src/**/*/types.ts", + "!src/index.tsx" + ], + "coverageThreshold": { + "global": { + "branches": 90, + "functions": 90, + "lines": 90, + "statements": 90 + } + } + }, + "devDependencies": { + "@babel/plugin-proposal-class-properties": "7.18.6", + "eslint-config-prettier": "8.8.0", + "eslint-plugin-prettier": "4.2.1", + "eslint-plugin-react-hooks": "4.6.0", + "html-webpack-plugin": "5.5.1", + "jest": "^29.5.0", + "mini-css-extract-plugin": "2.7.5" + } +} diff --git a/datajunction-ui/public/favicon.ico b/datajunction-ui/public/favicon.ico new file mode 100644 index 000000000..0707abe6f Binary files /dev/null and b/datajunction-ui/public/favicon.ico differ diff --git a/datajunction-ui/public/index.html b/datajunction-ui/public/index.html new file mode 100644 index 000000000..d42b092e0 --- /dev/null +++ b/datajunction-ui/public/index.html @@ -0,0 +1,26 @@ + + + + + + + + + + + + + DataJunction App + + + + +
+ + + + + diff --git a/datajunction-ui/public/manifest.json b/datajunction-ui/public/manifest.json new file mode 100644 index 000000000..167002495 --- /dev/null +++ b/datajunction-ui/public/manifest.json @@ -0,0 +1,15 @@ +{ + "short_name": "DataJunction UI", + "name": "DataJunction UI", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/datajunction-ui/public/robots.txt b/datajunction-ui/public/robots.txt new file mode 100644 index 000000000..e9e57dc4d --- /dev/null +++ b/datajunction-ui/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/datajunction-ui/src/app/__tests__/__snapshots__/index.test.tsx.snap b/datajunction-ui/src/app/__tests__/__snapshots__/index.test.tsx.snap new file mode 100644 index 000000000..9d3c7d883 --- /dev/null +++ b/datajunction-ui/src/app/__tests__/__snapshots__/index.test.tsx.snap @@ -0,0 +1,88 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` should render and match the snapshot 1`] = ` + + + + + + + } + path="/" + > + + + } + path=":name" + /> + + } + path="/" + /> + + } + path=":namespace" + /> + + } + path="sql" + /> + + + } + path="*" + /> + + + +`; diff --git a/datajunction-ui/src/app/__tests__/index.test.tsx b/datajunction-ui/src/app/__tests__/index.test.tsx new file mode 100644 index 000000000..7d3cd5b48 --- /dev/null +++ b/datajunction-ui/src/app/__tests__/index.test.tsx @@ -0,0 +1,14 @@ +import * as React from 'react'; +import { createRenderer } from 'react-test-renderer/shallow'; + +import { App } from '../index'; + +const renderer = createRenderer(); + +describe('', () => { + it('should render and match the snapshot', () => { + renderer.render(); + const renderedOutput = renderer.getRenderOutput(); + expect(renderedOutput).toMatchSnapshot(); + }); +}); diff --git a/datajunction-ui/src/app/components/ListGroupItem.jsx b/datajunction-ui/src/app/components/ListGroupItem.jsx new file mode 100644 index 000000000..9bccc244f --- /dev/null +++ b/datajunction-ui/src/app/components/ListGroupItem.jsx @@ -0,0 +1,17 @@ +import { Component } from 'react'; + +export default class ListGroupItem extends Component { + render() { + const { label, value } = this.props; + return ( +
+
+
+
{label}
+

{value}

+
+
+
+ ); + } +} diff --git a/datajunction-ui/src/app/components/NamespaceHeader.jsx b/datajunction-ui/src/app/components/NamespaceHeader.jsx new file mode 100644 index 000000000..4a48a3f5c --- /dev/null +++ b/datajunction-ui/src/app/components/NamespaceHeader.jsx @@ -0,0 +1,31 @@ +import { Component } from 'react'; +import HorizontalHierarchyIcon from '../icons/HorizontalHierarchyIcon'; + +export default class NamespaceHeader extends Component { + render() { + const { namespace } = this.props; + const namespaceParts = namespace.split('.'); + const namespaceList = namespaceParts.map((piece, index) => { + return ( +
  • + + {piece} + +
  • + ); + }); + return ( +
      +
    1. + + + +
    2. + {namespaceList} +
    + ); + } +} diff --git a/datajunction-ui/src/app/components/QueryInfo.jsx b/datajunction-ui/src/app/components/QueryInfo.jsx new file mode 100644 index 000000000..da2d652ee --- /dev/null +++ b/datajunction-ui/src/app/components/QueryInfo.jsx @@ -0,0 +1,77 @@ +export default function QueryInfo({ + id, + state, + engine_name, + engine_version, + errors, + links, + output_table, + scheduled, + started, + numRows, +}) { + return ( +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Query IDEngineStateScheduledStartedErrorsLinksOutput TableNumber of Rows
    + {id} + + + {engine_name} + {' - '} + {engine_version} + + {state}{scheduled}{started} + {errors?.length ? ( + errors.map(e => ( +

    + + {e} + +

    + )) + ) : ( + <> + )} +
    + {links?.length ? ( + links.map(link => ( +

    + + {link} + +

    + )) + ) : ( + <> + )} +
    {output_table}{numRows}
    + + ); +} diff --git a/datajunction-ui/src/app/components/Tab.jsx b/datajunction-ui/src/app/components/Tab.jsx new file mode 100644 index 000000000..b7d5ed29c --- /dev/null +++ b/datajunction-ui/src/app/components/Tab.jsx @@ -0,0 +1,18 @@ +import { Component } from 'react'; + +export default class Tab extends Component { + render() { + const { id, onClick, selectedTab } = this.props; + return ( +
    +
    +
    + +
    +
    +
    + ); + } +} diff --git a/datajunction-ui/src/app/components/ToggleSwitch.jsx b/datajunction-ui/src/app/components/ToggleSwitch.jsx new file mode 100644 index 000000000..c94dac7c2 --- /dev/null +++ b/datajunction-ui/src/app/components/ToggleSwitch.jsx @@ -0,0 +1,17 @@ +import React from 'react'; + +const ToggleSwitch = ({ checked, onChange, toggleName }) => ( + <> + onChange(e.target.checked)} + /> + {' '} + {toggleName} + +); + +export default ToggleSwitch; diff --git a/datajunction-ui/src/app/components/__tests__/ListGroupItem.test.tsx b/datajunction-ui/src/app/components/__tests__/ListGroupItem.test.tsx new file mode 100644 index 000000000..372af7ce5 --- /dev/null +++ b/datajunction-ui/src/app/components/__tests__/ListGroupItem.test.tsx @@ -0,0 +1,16 @@ +import * as React from 'react'; +import { createRenderer } from 'react-test-renderer/shallow'; + +import ListGroupItem from '../ListGroupItem'; + +const renderer = createRenderer(); + +describe('', () => { + it('should render and match the snapshot', () => { + renderer.render( + Something} />, + ); + const renderedOutput = renderer.getRenderOutput(); + expect(renderedOutput).toMatchSnapshot(); + }); +}); diff --git a/datajunction-ui/src/app/components/__tests__/NamespaceHeader.test.jsx b/datajunction-ui/src/app/components/__tests__/NamespaceHeader.test.jsx new file mode 100644 index 000000000..bb2f7f862 --- /dev/null +++ b/datajunction-ui/src/app/components/__tests__/NamespaceHeader.test.jsx @@ -0,0 +1,14 @@ +import * as React from 'react'; +import { createRenderer } from 'react-test-renderer/shallow'; + +import NamespaceHeader from '../NamespaceHeader'; + +const renderer = createRenderer(); + +describe('', () => { + it('should render and match the snapshot', () => { + renderer.render(); + const renderedOutput = renderer.getRenderOutput(); + expect(renderedOutput).toMatchSnapshot(); + }); +}); diff --git a/datajunction-ui/src/app/components/__tests__/__snapshots__/ListGroupItem.test.tsx.snap b/datajunction-ui/src/app/components/__tests__/__snapshots__/ListGroupItem.test.tsx.snap new file mode 100644 index 000000000..a5e889393 --- /dev/null +++ b/datajunction-ui/src/app/components/__tests__/__snapshots__/ListGroupItem.test.tsx.snap @@ -0,0 +1,26 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` should render and match the snapshot 1`] = ` +
    +
    +
    +
    + Name +
    +

    + + Something + +

    +
    +
    +
    +`; diff --git a/datajunction-ui/src/app/components/__tests__/__snapshots__/NamespaceHeader.test.jsx.snap b/datajunction-ui/src/app/components/__tests__/__snapshots__/NamespaceHeader.test.jsx.snap new file mode 100644 index 000000000..5a6c781b9 --- /dev/null +++ b/datajunction-ui/src/app/components/__tests__/__snapshots__/NamespaceHeader.test.jsx.snap @@ -0,0 +1,47 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` should render and match the snapshot 1`] = ` +
      +
    1. + + + +
    2. +
    3. + + shared + +
    4. +
    5. + + dimensions + +
    6. +
    7. + + accounts + +
    8. +
    +`; diff --git a/datajunction-ui/src/app/components/djgraph/Collapse.jsx b/datajunction-ui/src/app/components/djgraph/Collapse.jsx new file mode 100644 index 000000000..daf51a0fe --- /dev/null +++ b/datajunction-ui/src/app/components/djgraph/Collapse.jsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { DJNodeDimensions } from './DJNodeDimensions'; +import { DJNodeColumns } from './DJNodeColumns'; + +export default function Collapse({ collapsed, text, data }) { + const [isCollapsed, setIsCollapsed] = React.useState(collapsed); + + return ( + <> +
    + {data.type === 'metric' ? ( + + ) : ( + '' + )} +
    + {data.type !== 'metric' + ? isCollapsed + ? DJNodeColumns({ data: data, limit: 10 }) + : DJNodeColumns({ data: data, limit: 100 }) + : DJNodeDimensions(data)} +
    + {data.type !== 'metric' && data.column_names.length > 10 ? ( + + ) : ( + '' + )} +
    + + ); +} diff --git a/datajunction-ui/src/app/components/djgraph/DJNode.jsx b/datajunction-ui/src/app/components/djgraph/DJNode.jsx new file mode 100644 index 000000000..50ebdb575 --- /dev/null +++ b/datajunction-ui/src/app/components/djgraph/DJNode.jsx @@ -0,0 +1,89 @@ +import { memo, useLayoutEffect, useRef, useState } from 'react'; +import { Handle, Position } from 'reactflow'; +import Collapse from './Collapse'; + +function capitalize(string) { + return string.charAt(0).toUpperCase() + string.slice(1); +} + +export function DJNode({ id, data }) { + const handleWrapperStyle = { + display: 'flex', + position: 'absolute', + height: '100%', + flexDirection: 'column', + top: '50%', + justifyContent: 'space-between', + }; + const handleWrapperStyleRight = { ...handleWrapperStyle, ...{ right: 0 } }; + + const handleStyle = { + width: '12px', + height: '12px', + borderRadius: '12px', + background: 'transparent', + border: '4px solid transparent', + cursor: 'pointer', + position: 'absolute', + top: '0px', + left: 0, + }; + const handleStyleLeft = percentage => { + return { + ...handleStyle, + ...{ + transform: 'translate(-' + percentage + '%, -50%)', + }, + }; + }; + const highlightNodeClass = + data.is_current === true ? ' dj-node_highlight' : ''; + return ( + <> +
    +
    + +
    +
    +
    + {data.name + .split('.') + .slice(0, data.name.split('.').length - 1) + .join(' \u25B6 ')} +
    +
    + +
    + +
    +
    + + ); +} + +export default memo(DJNode); diff --git a/datajunction-ui/src/app/components/djgraph/DJNodeColumns.jsx b/datajunction-ui/src/app/components/djgraph/DJNodeColumns.jsx new file mode 100644 index 000000000..80d8750eb --- /dev/null +++ b/datajunction-ui/src/app/components/djgraph/DJNodeColumns.jsx @@ -0,0 +1,68 @@ +import { Handle } from 'reactflow'; +import React from 'react'; + +export function DJNodeColumns({ data, limit }) { + const handleWrapperStyle = { + display: 'flex', + position: 'absolute', + height: '100%', + flexDirection: 'column', + top: '50%', + justifyContent: 'space-between', + }; + const handleWrapperStyleRight = { ...handleWrapperStyle, ...{ right: 0 } }; + + const handleStyle = { + width: '12px', + height: '12px', + borderRadius: '12px', + background: 'transparent', + border: '4px solid transparent', + cursor: 'pointer', + position: 'absolute', + top: '0px', + left: 0, + }; + const handleStyleLeft = percentage => { + return { + ...handleStyle, + ...{ + transform: 'translate(-' + percentage + '%, -50%)', + }, + }; + }; + return data.column_names.slice(0, limit).map(col => ( +
    +
    + +
    +
    + {data.primary_key.includes(col.name) ? ( + {col.name} (PK) + ) : ( + <>{col.name} + )} + + {col.type} + +
    +
    + +
    +
    + )); +} diff --git a/datajunction-ui/src/app/components/djgraph/DJNodeDimensions.jsx b/datajunction-ui/src/app/components/djgraph/DJNodeDimensions.jsx new file mode 100644 index 000000000..d21e25db4 --- /dev/null +++ b/datajunction-ui/src/app/components/djgraph/DJNodeDimensions.jsx @@ -0,0 +1,68 @@ +import { useContext, useEffect, useState } from 'react'; +import DJClientContext from '../../providers/djclient'; + +export function DJNodeDimensions(data) { + const [dimensions, setDimensions] = useState([]); + const djClient = useContext(DJClientContext).DataJunctionAPI; + useEffect(() => { + if (data.type === 'metric') { + async function getDimensions() { + try { + const metricData = await djClient.metric(data.name); + setDimensions(metricData.dimensions); + } catch (err) { + console.log(err); + } + } + getDimensions(); + } + }, [data, djClient]); + const dimensionsToObject = dimensions => { + return dimensions.map(dim => { + const [attribute, ...nodeName] = dim.name.split('.').reverse(); + return { + dimension: nodeName.reverse().join('.'), + path: dim.path, + column: attribute, + }; + }); + }; + const groupedDimensions = dims => + dims.reduce((acc, current) => { + const dimKey = current.dimension + ' via ' + current.path.slice(-1); + acc[dimKey] = acc[dimKey] || { + dimension: current.dimension, + path: current.path.slice(-1), + columns: [], + }; + acc[dimKey].columns.push(current.column); + return acc; + }, {}); + const dimensionsRenderer = grouped => + Object.entries(grouped).map(([dimKey, dimValue]) => { + if (Array.isArray(dimValue.columns)) { + const attributes = dimValue.columns.map(col => { + return {col}; + }); + return ( +
    +
    + {dimValue.dimension}{' '} +
    + {dimValue.path} +
    +
    +
    {attributes}
    +
    + ); + } + return <>; + }); + return ( + <> + {dimensions.length <= 0 + ? '' + : dimensionsRenderer(groupedDimensions(dimensionsToObject(dimensions)))} + + ); +} diff --git a/datajunction-ui/src/app/components/djgraph/LayoutFlow.jsx b/datajunction-ui/src/app/components/djgraph/LayoutFlow.jsx new file mode 100644 index 000000000..e5c0400a4 --- /dev/null +++ b/datajunction-ui/src/app/components/djgraph/LayoutFlow.jsx @@ -0,0 +1,104 @@ +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import ReactFlow, { + addEdge, + MiniMap, + Controls, + Background, + useNodesState, + useEdgesState, +} from 'reactflow'; + +import '../../../styles/dag.css'; +import 'reactflow/dist/style.css'; +import DJNode from '../../components/djgraph/DJNode'; +import dagre from 'dagre'; + +const getLayoutedElements = ( + nodes, + edges, + direction = 'LR', + nodeWidth = 600, +) => { + const dagreGraph = new dagre.graphlib.Graph(); + dagreGraph.setDefaultEdgeLabel(() => ({})); + + const isHorizontal = direction === 'TB'; + dagreGraph.setGraph({ + rankdir: direction, + nodesep: 40, + ranksep: 10, + ranker: 'longest-path', + }); + const nodeHeightTracker = {}; + + nodes.forEach(node => { + nodeHeightTracker[node.id] = + Math.min(node.data.column_names.length, 10) * 40 + 250; + dagreGraph.setNode(node.id, { + width: nodeWidth, + height: nodeHeightTracker[node.id], + }); + }); + + edges.forEach(edge => { + dagreGraph.setEdge(edge.source, edge.target); + }); + + dagre.layout(dagreGraph); + + nodes.forEach(node => { + const nodeWithPosition = dagreGraph.node(node.id); + node.targetPosition = isHorizontal ? 'left' : 'top'; + node.sourcePosition = isHorizontal ? 'right' : 'bottom'; + node.position = { + x: nodeWithPosition.x - nodeWidth / 2, + y: nodeWithPosition.y - nodeHeightTracker[node.id] / 2, + }; + node.width = nodeWidth; + node.height = nodeHeightTracker[node.id]; + return node; + }); + + return { nodes: nodes, edges: edges }; +}; + +const LayoutFlow = (djNode, saveGraph) => { + const nodeTypes = useMemo(() => ({ DJNode: DJNode }), []); + + // These are used internally by ReactFlow (to update the nodes on the ReactFlow pane) + const [nodes, setNodes, onNodesChange] = useNodesState([]); + const [edges, setEdges, onEdgesChange] = useEdgesState([]); + + const minimapStyle = { + height: 100, + width: 150, + }; + + useEffect(() => { + saveGraph(getLayoutedElements, setNodes, setEdges).catch(console.error); + }, [djNode]); + + const onConnect = useCallback( + params => setEdges(eds => addEdge(params, eds)), + [setEdges], + ); + return ( +
    + + + + + +
    + ); +}; +export default LayoutFlow; diff --git a/datajunction-ui/src/app/components/djgraph/__tests__/DJNode.test.tsx b/datajunction-ui/src/app/components/djgraph/__tests__/DJNode.test.tsx new file mode 100644 index 000000000..478c51a2e --- /dev/null +++ b/datajunction-ui/src/app/components/djgraph/__tests__/DJNode.test.tsx @@ -0,0 +1,24 @@ +import * as React from 'react'; +import { createRenderer } from 'react-test-renderer/shallow'; + +import { DJNode } from '../DJNode'; + +const renderer = createRenderer(); + +describe('', () => { + it('should render and match the snapshot', () => { + renderer.render( + , + ); + const renderedOutput = renderer.getRenderOutput(); + expect(renderedOutput).toMatchSnapshot(); + }); +}); diff --git a/datajunction-ui/src/app/components/djgraph/__tests__/__snapshots__/DJNode.test.tsx.snap b/datajunction-ui/src/app/components/djgraph/__tests__/__snapshots__/DJNode.test.tsx.snap new file mode 100644 index 000000000..dc168caf5 --- /dev/null +++ b/datajunction-ui/src/app/components/djgraph/__tests__/__snapshots__/DJNode.test.tsx.snap @@ -0,0 +1,117 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` should render and match the snapshot 1`] = ` + +
    +
    + +
    +
    +
    + shared ▶ dimensions +
    +
    +
    + + Source + +
    + + + +
    +
    + +
    +
    +
    +`; diff --git a/datajunction-ui/src/app/icons/CollapsedIcon.jsx b/datajunction-ui/src/app/icons/CollapsedIcon.jsx new file mode 100644 index 000000000..53318555d --- /dev/null +++ b/datajunction-ui/src/app/icons/CollapsedIcon.jsx @@ -0,0 +1,15 @@ +const CollapsedIcon = props => ( + + + +); + +export default CollapsedIcon; diff --git a/datajunction-ui/src/app/icons/ExpandedIcon.jsx b/datajunction-ui/src/app/icons/ExpandedIcon.jsx new file mode 100644 index 000000000..bfa45e140 --- /dev/null +++ b/datajunction-ui/src/app/icons/ExpandedIcon.jsx @@ -0,0 +1,15 @@ +const ExpandedIcon = props => ( + + + +); + +export default ExpandedIcon; diff --git a/datajunction-ui/src/app/icons/HorizontalHierarchyIcon.jsx b/datajunction-ui/src/app/icons/HorizontalHierarchyIcon.jsx new file mode 100644 index 000000000..183897cae --- /dev/null +++ b/datajunction-ui/src/app/icons/HorizontalHierarchyIcon.jsx @@ -0,0 +1,15 @@ +const HorizontalHierarchyIcon = props => ( + + + +); + +export default HorizontalHierarchyIcon; diff --git a/datajunction-ui/src/app/icons/InvalidIcon.jsx b/datajunction-ui/src/app/icons/InvalidIcon.jsx new file mode 100644 index 000000000..d59a41ece --- /dev/null +++ b/datajunction-ui/src/app/icons/InvalidIcon.jsx @@ -0,0 +1,14 @@ +const InvalidIcon = props => ( + + + +); + +export default InvalidIcon; diff --git a/datajunction-ui/src/app/icons/PythonIcon.jsx b/datajunction-ui/src/app/icons/PythonIcon.jsx new file mode 100644 index 000000000..219618a6c --- /dev/null +++ b/datajunction-ui/src/app/icons/PythonIcon.jsx @@ -0,0 +1,52 @@ +const PythonIcon = props => ( + + + + + + + + + + + + + + + + + + +); + +export default PythonIcon; diff --git a/datajunction-ui/src/app/icons/TableIcon.jsx b/datajunction-ui/src/app/icons/TableIcon.jsx new file mode 100644 index 000000000..e56776ec4 --- /dev/null +++ b/datajunction-ui/src/app/icons/TableIcon.jsx @@ -0,0 +1,14 @@ +const TableIcon = props => ( + + + +); + +export default TableIcon; diff --git a/datajunction-ui/src/app/icons/ValidIcon.jsx b/datajunction-ui/src/app/icons/ValidIcon.jsx new file mode 100644 index 000000000..b8f7cdfbf --- /dev/null +++ b/datajunction-ui/src/app/icons/ValidIcon.jsx @@ -0,0 +1,14 @@ +const ValidIcon = props => ( + + + +); + +export default ValidIcon; diff --git a/datajunction-ui/src/app/index.tsx b/datajunction-ui/src/app/index.tsx new file mode 100644 index 000000000..89d551168 --- /dev/null +++ b/datajunction-ui/src/app/index.tsx @@ -0,0 +1,58 @@ +/** + * This component is the skeleton around the actual pages, and only contains + * components that should be seen on all pages, like the logo or navigation bar. + */ + +import * as React from 'react'; +import { Helmet } from 'react-helmet-async'; +import { BrowserRouter, Routes, Route } from 'react-router-dom'; + +import { NamespacePage } from './pages/NamespacePage/Loadable'; +import { NodePage } from './pages/NodePage/Loadable'; +import { SQLBuilderPage } from './pages/SQLBuilderPage/Loadable'; +import { NotFoundPage } from './pages/NotFoundPage/Loadable'; +import { Root } from './pages/Root/Loadable'; +import DJClientContext from './providers/djclient'; +import { DataJunctionAPI } from './services/DJService'; + +export function App() { + return ( + + + + + + + } + children={ + <> + + } /> + + + } key="index" /> + + } + key="namespaces" + /> + + } /> + + } + /> + } /> + + + + ); +} diff --git a/datajunction-ui/src/app/pages/NamespacePage/Explorer.jsx b/datajunction-ui/src/app/pages/NamespacePage/Explorer.jsx new file mode 100644 index 000000000..a86d25c4d --- /dev/null +++ b/datajunction-ui/src/app/pages/NamespacePage/Explorer.jsx @@ -0,0 +1,57 @@ +import React, { useEffect, useState } from 'react'; +import CollapsedIcon from '../../icons/CollapsedIcon'; +import ExpandedIcon from '../../icons/ExpandedIcon'; + +const Explorer = ({ item = [], current }) => { + const [items, setItems] = useState([]); + const [expand, setExpand] = useState(false); + const [highlight, setHighlight] = useState(false); + + useEffect(() => { + setItems(item); + setHighlight(current); + if (current === undefined || current?.startsWith(item.path)) { + setExpand(true); + } else setExpand(false); + }, [current, item]); + + const handleClickOnParent = e => { + e.stopPropagation(); + setExpand(prev => { + return !prev; + }); + }; + + return ( + <> + + {items.children + ? items.children.map((item, index) => ( +
    +
    + +
    +
    + )) + : null} + + ); +}; + +export default Explorer; diff --git a/datajunction-ui/src/app/pages/NamespacePage/Loadable.jsx b/datajunction-ui/src/app/pages/NamespacePage/Loadable.jsx new file mode 100644 index 000000000..d4fa9fff2 --- /dev/null +++ b/datajunction-ui/src/app/pages/NamespacePage/Loadable.jsx @@ -0,0 +1,16 @@ +/** + * Asynchronously loads the component for namespaces node-viewing page + */ + +import * as React from 'react'; +import { lazyLoad } from '../../../utils/loadable'; + +export const NamespacePage = props => { + return lazyLoad( + () => import('./index'), + module => module.NamespacePage, + { + fallback:
    , + }, + )(props); +}; diff --git a/datajunction-ui/src/app/pages/NamespacePage/__tests__/__snapshots__/index.test.tsx.snap b/datajunction-ui/src/app/pages/NamespacePage/__tests__/__snapshots__/index.test.tsx.snap new file mode 100644 index 000000000..3d3bd852b --- /dev/null +++ b/datajunction-ui/src/app/pages/NamespacePage/__tests__/__snapshots__/index.test.tsx.snap @@ -0,0 +1,70 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` should render and match the snapshot 1`] = ` +
    +
    +
    +

    + Explore +

    +
    +
    + + Namespaces + +
    + + + + + + + + + + + + + +
    + Name + + Display Name + + Type + + Status + + Mode + + Tags + + Last Updated +
    +
    +
    +
    +
    +`; diff --git a/datajunction-ui/src/app/pages/NamespacePage/__tests__/index.test.tsx b/datajunction-ui/src/app/pages/NamespacePage/__tests__/index.test.tsx new file mode 100644 index 000000000..6dc69b96b --- /dev/null +++ b/datajunction-ui/src/app/pages/NamespacePage/__tests__/index.test.tsx @@ -0,0 +1,14 @@ +import * as React from 'react'; +import { createRenderer } from 'react-test-renderer/shallow'; + +import { NamespacePage } from '../index'; + +const renderer = createRenderer(); + +describe('', () => { + it('should render and match the snapshot', () => { + renderer.render(); + const renderedOutput = renderer.getRenderOutput(); + expect(renderedOutput).toMatchSnapshot(); + }); +}); diff --git a/datajunction-ui/src/app/pages/NamespacePage/index.jsx b/datajunction-ui/src/app/pages/NamespacePage/index.jsx new file mode 100644 index 000000000..e48680049 --- /dev/null +++ b/datajunction-ui/src/app/pages/NamespacePage/index.jsx @@ -0,0 +1,161 @@ +import * as React from 'react'; +import { useParams } from 'react-router-dom'; +import { useContext, useEffect, useState } from 'react'; +import NodeStatus from '../NodePage/NodeStatus'; +import DJClientContext from '../../providers/djclient'; +import Explorer from '../NamespacePage/Explorer'; + +export function NamespacePage() { + const djClient = useContext(DJClientContext).DataJunctionAPI; + var { namespace } = useParams(); + + const [state, setState] = useState({ + namespace: namespace, + nodes: [], + }); + + const [namespaceHierarchy, setNamespaceHierarchy] = useState([]); + + const createNamespaceHierarchy = namespaceList => { + const hierarchy = []; + + for (const item of namespaceList) { + const namespaces = item.split('.'); + let currentLevel = hierarchy; + + let path = ''; + for (const ns of namespaces) { + path += ns; + + let existingNamespace = currentLevel.find(el => el.namespace === ns); + if (!existingNamespace) { + existingNamespace = { + namespace: ns, + children: [], + path: path, + }; + currentLevel.push(existingNamespace); + } + + currentLevel = existingNamespace.children; + path += '.'; + } + } + return hierarchy; + }; + + useEffect(() => { + const fetchData = async () => { + const namespaces = await djClient.namespaces(); + const hierarchy = createNamespaceHierarchy(namespaces); + setNamespaceHierarchy(hierarchy); + }; + fetchData().catch(console.error); + }, [djClient, djClient.namespaces]); + + useEffect(() => { + const fetchData = async () => { + if (namespace === undefined && namespaceHierarchy !== undefined) { + namespace = namespaceHierarchy.children[0].path; + } + const djNodes = await djClient.namespace(namespace); + const nodes = djNodes.map(node => { + return djClient.node(node); + }); + const foundNodes = await Promise.all(nodes); + setState({ + namespace: namespace, + nodes: foundNodes, + }); + }; + fetchData().catch(console.error); + }, [djClient, namespace, namespaceHierarchy]); + + const nodesList = state.nodes.map(node => ( + + + + {node.name} + + + {node.version} + + + + + {node.display_name} + + + + + {node.type} + + + + + + + {node.mode} + + + {node.tags} + + + + {new Date(node.updated_at).toLocaleString('en-us')} + + + + )); + + return ( +
    +
    +
    +

    Explore

    +
    +
    + + Namespaces + + {namespaceHierarchy + ? namespaceHierarchy.map(child => ( + + )) + : null} +
    + + + + + + + + + + + + + {nodesList} +
    NameDisplay NameTypeStatusModeTagsLast Updated
    +
    +
    +
    +
    + ); +} diff --git a/datajunction-ui/src/app/pages/NodePage/ClientCodePopover.jsx b/datajunction-ui/src/app/pages/NodePage/ClientCodePopover.jsx new file mode 100644 index 000000000..fad4d5568 --- /dev/null +++ b/datajunction-ui/src/app/pages/NodePage/ClientCodePopover.jsx @@ -0,0 +1,30 @@ +import { Light as SyntaxHighlighter } from 'react-syntax-highlighter'; +import { useState } from 'react'; +import { nightOwl } from 'react-syntax-highlighter/src/styles/hljs'; +import PythonIcon from '../../icons/PythonIcon'; + +export default function ClientCodePopover({ code }) { + const [codeAnchor, setCodeAnchor] = useState(false); + + return ( + <> + +
    setCodeAnchor(null)} + style={{ display: codeAnchor === false ? 'none' : 'block' }} + > + + {code} + +
    + + ); +} diff --git a/datajunction-ui/src/app/pages/NodePage/Loadable.jsx b/datajunction-ui/src/app/pages/NodePage/Loadable.jsx new file mode 100644 index 000000000..1461404fd --- /dev/null +++ b/datajunction-ui/src/app/pages/NodePage/Loadable.jsx @@ -0,0 +1,16 @@ +/** + * Asynchronously loads the component for the Node page + */ + +import * as React from 'react'; +import { lazyLoad } from '../../../utils/loadable'; + +export const NodePage = props => { + return lazyLoad( + () => import('./index'), + module => module.NodePage, + { + fallback:
    , + }, + )(props); +}; diff --git a/datajunction-ui/src/app/pages/NodePage/NodeColumnTab.jsx b/datajunction-ui/src/app/pages/NodePage/NodeColumnTab.jsx new file mode 100644 index 000000000..259c7e817 --- /dev/null +++ b/datajunction-ui/src/app/pages/NodePage/NodeColumnTab.jsx @@ -0,0 +1,60 @@ +import { useEffect, useState } from 'react'; +import ClientCodePopover from './ClientCodePopover'; + +export default function NodeColumnTab({ node, djClient }) { + const [columns, setColumns] = useState([]); + useEffect(() => { + const fetchData = async () => { + setColumns(await djClient.columns(node)); + }; + fetchData().catch(console.error); + }, [djClient, node]); + + const columnList = columns => { + return columns.map(col => ( + + {col.name} + + + {col.type} + + + + {col.dimension ? ( + <> + {col.dimension.name} + + + ) : ( + '' + )}{' '} + + + {col.attributes.find( + attr => attr.attribute_type.name === 'dimension', + ) ? ( + + dimensional + + ) : ( + '' + )} + + + )); + }; + + return ( +
    + + + + + + + + {columnList(columns)} +
    ColumnTypeDimensionAttributes
    +
    + ); +} diff --git a/datajunction-ui/src/app/pages/NodePage/NodeGraphTab.jsx b/datajunction-ui/src/app/pages/NodePage/NodeGraphTab.jsx new file mode 100644 index 000000000..c1d90f458 --- /dev/null +++ b/datajunction-ui/src/app/pages/NodePage/NodeGraphTab.jsx @@ -0,0 +1,113 @@ +import React, { useContext } from 'react'; +import { MarkerType } from 'reactflow'; + +import '../../../styles/dag.css'; +import 'reactflow/dist/style.css'; +import DJNode from '../../components/djgraph/DJNode'; +import DJClientContext from '../../providers/djclient'; +import LayoutFlow from '../../components/djgraph/LayoutFlow'; + +const NodeLineage = djNode => { + const djClient = useContext(DJClientContext).DataJunctionAPI; + + const createNode = node => { + const primary_key = node.columns + .filter(col => + col.attributes.some(attr => attr.attribute_type.name === 'primary_key'), + ) + .map(col => col.name); + const column_names = node.columns.map(col => { + return { name: col.name, type: col.type }; + }); + return { + id: String(node.name), + type: 'DJNode', + data: { + label: + node.table !== null + ? String(node.schema_ + '.' + node.table) + : 'default.' + node.name, + table: node.table, + name: String(node.name), + display_name: String(node.display_name), + type: node.type, + primary_key: primary_key, + column_names: column_names, + is_current: node.name === djNode.djNode.name, + }, + }; + }; + + const dimensionEdges = node => { + return node.columns + .filter(col => col.dimension) + .map(col => { + return { + id: col.dimension.name + '->' + node.name + '.' + col.name, + source: col.dimension.name, + sourceHandle: col.dimension.name, + target: node.name, + targetHandle: node.name + '.' + col.name, + draggable: true, + markerStart: { + type: MarkerType.Arrow, + width: 20, + height: 20, + color: '#b0b9c2', + }, + style: { + strokeWidth: 3, + stroke: '#b0b9c2', + }, + }; + }); + }; + + const parentEdges = node => { + return node.parents + .filter(parent => parent.name) + .map(parent => { + return { + id: node.name + '-' + parent.name, + source: parent.name, + sourceHandle: parent.name, + target: node.name, + targetHandle: node.name, + animated: true, + markerEnd: { + type: MarkerType.Arrow, + }, + style: { + strokeWidth: 3, + stroke: '#b0b9c2', + }, + }; + }); + }; + + const dagFetch = async (getLayoutedElements, setNodes, setEdges) => { + let related_nodes = await djClient.node_dag(djNode.djNode.name); + var djNodes = [djNode.djNode]; + for (const iterable of [related_nodes]) { + for (const item of iterable) { + if (item.type !== 'cube') { + djNodes.push(item); + } + } + } + let edges = []; + djNodes.forEach(node => { + edges = edges.concat(parentEdges(node)); + edges = edges.concat(dimensionEdges(node)); + }); + const nodes = djNodes.map(node => createNode(node)); + + // use dagre to determine the position of the parents (the DJ nodes) + // the positions of the columns are relative to each DJ node + getLayoutedElements(nodes, edges); + setNodes(nodes); + setEdges(edges); + }; + return LayoutFlow(djNode, dagFetch); +}; +export default NodeLineage; diff --git a/datajunction-ui/src/app/pages/NodePage/NodeHistory.jsx b/datajunction-ui/src/app/pages/NodePage/NodeHistory.jsx new file mode 100644 index 000000000..26c9ff46e --- /dev/null +++ b/datajunction-ui/src/app/pages/NodePage/NodeHistory.jsx @@ -0,0 +1,180 @@ +import { useEffect, useState } from 'react'; + +export default function NodeHistory({ node, djClient }) { + const [history, setHistory] = useState([]); + const [revisions, setRevisions] = useState([]); + + useEffect(() => { + const fetchData = async () => { + const data = await djClient.history('node', node.name); + setHistory(data); + const revisions = await djClient.revisions(node.name); + setRevisions(revisions); + }; + fetchData().catch(console.error); + }, [djClient, node]); + + const eventData = event => { + console.log('event', event); + if ( + event.activity_type === 'set_attribute' && + event.entity_type === 'column_attribute' + ) { + return event.details.attributes + .map(attr => ( +
    + Set{' '} + {attr.column_name}{' '} + as{' '} + + {attr.attribute_type_name} + +
    + )) + .reduce((prev, curr) => [prev,
    , curr]); + } + if (event.activity_type === 'create' && event.entity_type === 'link') { + return ( +
    + Linked{' '} + + {event.details.column} + {' '} + to + + {event.details.dimension} + {' '} + via + + {event.details.dimension_column} + +
    + ); + } + if ( + event.activity_type === 'create' && + event.entity_type === 'materialization' + ) { + return ( +
    + Initialized materialization{' '} + + {event.details.materialization} + +
    + ); + } + if ( + event.activity_type === 'create' && + event.entity_type === 'availability' + ) { + return ( +
    + Materialized at{' '} + + {event.post.catalog}.{event.post.schema_}.{event.post.table} + + from{' '} + + {event.post.min_temporal_partition} + {' '} + to + + {event.post.max_temporal_partition} + +
    + ); + } + if ( + event.activity_type === 'status_change' && + event.entity_type === 'node' + ) { + const expr = ( +
    + Caused by a change in upstream{' '} + + {event.details['upstream_node']} + +
    + ); + return ( +
    + Status changed from{' '} + + {event.pre['status']} + {' '} + to{' '} + + {event.post['status']} + {' '} + {event.details['upstream_node'] !== undefined ? expr : ''} +
    + ); + } + return ( +
    + {JSON.stringify(event.details) === '{}' + ? '' + : JSON.stringify(event.details)} +
    + ); + }; + + const tableData = history => { + return history.map(event => ( + + + + {event.activity_type} + + + {event.entity_type} + {event.entity_name} + {event.user ? event.user : 'unknown'} + {event.created_at} + {eventData(event)} + + )); + }; + + const revisionsTable = revisions => { + return revisions.map(revision => ( + + + {revision.version} + + {revision.display_name} + {revision.description} + {revision.query} + {revision.tags} + + )); + }; + return ( +
    + + + + + + + + + {revisionsTable(revisions)} +
    VersionDisplay NameDescriptionQueryTags
    + + + + + + + + + + {tableData(history)} +
    ActivityTypeNameUserTimestampDetails
    +
    + ); +} diff --git a/datajunction-ui/src/app/pages/NodePage/NodeInfoTab.jsx b/datajunction-ui/src/app/pages/NodePage/NodeInfoTab.jsx new file mode 100644 index 000000000..d49d37c7e --- /dev/null +++ b/datajunction-ui/src/app/pages/NodePage/NodeInfoTab.jsx @@ -0,0 +1,157 @@ +import { useState, useContext, useEffect } from 'react'; +import { Light as SyntaxHighlighter } from 'react-syntax-highlighter'; +import { foundation } from 'react-syntax-highlighter/src/styles/hljs'; +import sql from 'react-syntax-highlighter/dist/esm/languages/hljs/sql'; +import NodeStatus from './NodeStatus'; +import ListGroupItem from '../../components/ListGroupItem'; +import ToggleSwitch from '../../components/ToggleSwitch'; +import DJClientContext from '../../providers/djclient'; + +SyntaxHighlighter.registerLanguage('sql', sql); +foundation.hljs['padding'] = '2rem'; + +export default function NodeInfoTab({ node }) { + const [compiledSQL, setCompiledSQL] = useState(''); + const [checked, setChecked] = useState(false); + const nodeTags = node?.tags.map(tag =>
    {tag}
    ); + const djClient = useContext(DJClientContext).DataJunctionAPI; + useEffect(() => { + const fetchData = async () => { + if (checked === true) { + const data = await djClient.compiledSql(node.name); + if (data.sql) { + setCompiledSQL(data.sql); + } else { + setCompiledSQL( + '/* Ran into an issue while generating compiled SQL */', + ); + } + } + }; + fetchData().catch(console.error); + }, [node, djClient, checked]); + function toggle(value) { + return !value; + } + const queryDiv = node?.query ? ( +
    +
    +
    +
    Query
    + {['metric', 'dimension', 'transform'].indexOf(node?.type) > -1 ? ( + setChecked(toggle)} + toggleName="Show Compiled SQL" + /> + ) : ( + <> + )} + + {checked ? compiledSQL : node?.query} + +
    +
    +
    + ) : ( + <> + ); + + const cubeElementsDiv = node?.cube_elements ? ( +
    +
    +
    +
    Cube Elements
    +
    + {node.cube_elements.map(cubeElem => ( +
    + + {cubeElem.type === 'metric' + ? cubeElem.node_name + : cubeElem.name} + + + {cubeElem.type === 'metric' ? cubeElem.type : 'dimension'} + +
    + ))} +
    +
    +
    +
    + ) : ( + <> + ); + return ( +
    + +
    +
    +
    +
    Version
    + +

    + + {node?.version} + +

    +
    + {node.type === 'source' ? ( +
    +
    Table
    +

    + {node?.catalog.name}.{node?.schema_}.{node?.table} +

    +
    + ) : ( + <> + )} +
    +
    Status
    +

    + +

    +
    +
    +
    Mode
    +

    + {node?.mode} +

    +
    +
    +
    Tags
    +

    {nodeTags}

    +
    +
    +
    Primary Key
    +

    {node?.primary_key}

    +
    +
    +
    Last Updated
    +

    + {new Date(node?.updated_at).toDateString()} +

    +
    +
    +
    + {node?.type !== 'cube' ? queryDiv : ''} + {cubeElementsDiv} +
    + ); +} diff --git a/datajunction-ui/src/app/pages/NodePage/NodeLineageTab.jsx b/datajunction-ui/src/app/pages/NodePage/NodeLineageTab.jsx new file mode 100644 index 000000000..f8bb09391 --- /dev/null +++ b/datajunction-ui/src/app/pages/NodePage/NodeLineageTab.jsx @@ -0,0 +1,84 @@ +import { useContext } from 'react'; +import { MarkerType } from 'reactflow'; + +import '../../../styles/dag.css'; +import 'reactflow/dist/style.css'; +import DJClientContext from '../../providers/djclient'; +import LayoutFlow from '../../components/djgraph/LayoutFlow'; + +const createDJNode = node => { + return { + id: node.node_name, + type: 'DJNode', + data: { + label: node.node_name, + name: node.node_name, + type: node.node_type, + table: node.node_type === 'source' ? node.node_name : '', + display_name: + node.node_type === 'source' ? node.node_name : node.display_name, + column_names: [{ name: node.column_name, type: '' }], + primary_key: [], + }, + }; +}; + +const NodeColumnLineage = djNode => { + const djClient = useContext(DJClientContext).DataJunctionAPI; + const dagFetch = async (getLayoutedElements, setNodes, setEdges) => { + let relatedNodes = await djClient.node_lineage(djNode.djNode.name); + let nodesMapping = {}; + let edgesMapping = {}; + let processing = relatedNodes; + while (processing.length > 0) { + let current = processing.pop(); + let node = createDJNode(current); + if (node.id in nodesMapping) { + nodesMapping[node.id].data.column_names = Array.from( + new Set([ + ...nodesMapping[node.id].data.column_names.map(x => x.name), + ...node.data.column_names.map(x => x.name), + ]), + ).map(x => { + return { name: x, type: '' }; + }); + } else { + nodesMapping[node.id] = node; + } + + current.lineage.forEach(lineageColumn => { + const sourceHandle = + lineageColumn.node_name + '.' + lineageColumn.column_name; + const targetHandle = current.node_name + '.' + current.column_name; + edgesMapping[sourceHandle + '->' + targetHandle] = { + id: sourceHandle + '->' + targetHandle, + source: lineageColumn.node_name, + sourceHandle: sourceHandle, + target: current.node_name, + targetHandle: targetHandle, + animated: true, + markerEnd: { + type: MarkerType.Arrow, + }, + style: { + strokeWidth: 3, + stroke: '#b0b9c2', + }, + }; + processing.push(lineageColumn); + }); + } + + // use dagre to determine the position of the parents (the DJ nodes) + // the positions of the columns are relative to each DJ node + const elements = getLayoutedElements( + Object.keys(nodesMapping).map(key => nodesMapping[key]), + Object.keys(edgesMapping).map(key => edgesMapping[key]), + ); + + setNodes(elements.nodes); + setEdges(elements.edges); + }; + return LayoutFlow(djNode, dagFetch); +}; +export default NodeColumnLineage; diff --git a/datajunction-ui/src/app/pages/NodePage/NodeMaterializationTab.jsx b/datajunction-ui/src/app/pages/NodePage/NodeMaterializationTab.jsx new file mode 100644 index 000000000..da5b1ba32 --- /dev/null +++ b/datajunction-ui/src/app/pages/NodePage/NodeMaterializationTab.jsx @@ -0,0 +1,151 @@ +import { useEffect, useState } from 'react'; +import ClientCodePopover from './ClientCodePopover'; +import TableIcon from '../../icons/TableIcon'; + +const cronstrue = require('cronstrue'); + +export default function NodeMaterializationTab({ node, djClient }) { + const [materializations, setMaterializations] = useState([]); + useEffect(() => { + const fetchData = async () => { + const data = await djClient.materializations(node.name); + setMaterializations(data); + }; + fetchData().catch(console.error); + }, [djClient, node]); + + const rangePartition = partition => { + return ( +
    + + {partition.range[0]}to + {partition.range[1]} + +
    + ); + }; + + const cron = materialization => { + var parsedCron = ''; + try { + parsedCron = cronstrue.toString(materialization.schedule); + } catch (e) {} + return parsedCron; + }; + + const materializationRows = materializations => { + return materializations.map(materialization => ( + + + {materialization.name} + + + + {materialization.schedule} +
    {cron(materialization)}
    + + + {materialization.engine.name} +
    + {materialization.engine.version} + + + {materialization.config.partitions ? ( + materialization.config.partitions.map(partition => + partition.type_ === 'categorical' ? ( +
    +
    {partition.name}
    +
    + {partition.values !== null && partition.values.length > 0 + ? partition.values.map(val => ( + {val} + )) + : null} + {partition.range !== null && partition.range.length > 0 + ? rangePartition(partition) + : null} + {(partition.range === null && partition.values === null) || + (partition.range.length === 0 && + partition.values.length === 0) ? ( + + ALL + + ) : null} +
    +
    + ) : null, + ) + ) : ( +
    + )} + + + {materialization.output_tables.map(table => ( +
    +
    + {' '} + + {table.split('.')[0] + '.' + table.split('.')[1]} + +
    +
    + {table.split('.')[2]} +
    +
    + ))} + + {/*{Object.keys(materialization.config.spark).map(key =>
  • {key}: {materialization.config.spark[key]}
  • )}*/} + + + {materialization.config.partitions ? ( + materialization.config.partitions.map(partition => + partition.type_ === 'temporal' ? ( +
    +
    {partition.name}
    +
    + {partition.values !== null && partition.values.length > 0 + ? partition.values.map(val => ( + {val} + )) + : null} + {partition.range !== null && partition.range.length > 0 + ? rangePartition(partition) + : null} +
    +
    + ) : null, + ) + ) : ( +
    + )} + + + {materialization.urls.map((url, idx) => ( + [{idx + 1}] + ))} + + + )); + }; + return ( +
    + + + + + + + + + + + {materializationRows( + materializations.filter( + materialization => + !(materialization.name === 'default' && node.type === 'cube'), + ), + )} +
    NameScheduleEnginePartitionsOutput TablesBackfillsURLs
    +
    + ); +} diff --git a/datajunction-ui/src/app/pages/NodePage/NodeSQLTab.jsx b/datajunction-ui/src/app/pages/NodePage/NodeSQLTab.jsx new file mode 100644 index 000000000..1ce40eb35 --- /dev/null +++ b/datajunction-ui/src/app/pages/NodePage/NodeSQLTab.jsx @@ -0,0 +1,92 @@ +import { useContext, useEffect, useState } from 'react'; +import Select from 'react-select'; +import DJClientContext from '../../providers/djclient'; +import { Light as SyntaxHighlighter } from 'react-syntax-highlighter'; +import { foundation } from 'react-syntax-highlighter/src/styles/hljs'; + +const NodeSQLTab = djNode => { + const djClient = useContext(DJClientContext).DataJunctionAPI; + const [query, setQuery] = useState(''); + + const [selection, setSelection] = useState({ + dimensions: [], + filters: [], + }); + + useEffect(() => { + const fetchData = async () => { + const query = await djClient.sql(djNode.djNode.name, selection); + setQuery(query.sql); + }; + fetchData().catch(console.error); + }, [djClient, djNode.djNode.name, selection]); + const dimensionsList = djNode.djNode.dimensions + ? djNode.djNode.dimensions.map(dim => ({ + value: dim.name, + label: dim.name + ` (${dim.type})`, + })) + : ['']; + + // const options = [ + // { value: '>=', label: '>=' }, + // { value: '<=', label: '<=' }, + // { value: '>', label: '>' }, + // { value: '<', label: '<' }, + // { value: '=', label: '=' }, + // { value: '!=', label: '!=' }, + // { value: 'IN', label: 'IN' }, + // ]; + + const handleSubmit = event => { + event.preventDefault(); + }; + + const handleChange = event => { + setSelection({ filters: [], dimensions: event.map(dim => dim.value) }); + }; + + return ( +
    +
    +

    Group By

    +